Миссия1: Некорректный стартовый адрес трека
Для создания защищенного диска с искаженным TOC нам понадобиться: любая программа записи на диск, умеющая создавать многосессионные диски (например, Roxio Easy CD Creator), копировщик защищенных дисков, сохраняющий содержимое TOC в текстовом файле, доступном для редактирования (мы выбираем Clone CDCloneCD) и, естественно, сам пишущий привод, поддерживающий режим "сырой" записи в режиме DAO. Для облегчения восприятия материала все действия будут расписаны по шагам, хотя это выглядит и не слишком литературно.
Шаг первый. Создание оригинального диска.
Достаем из упаковки новую болванку CD-R или, – что даже лучше, – вставляем в привод "потертый жизнью" диск CD-RW и записываем на него пару сессий в штатном режиме. Будет лучше (вернее нагляднее) если вторая сессия будет включать в себя файлы первой сессии, – той самой сессии, чей TOC мы и собираемся искажать. Интересно, сможет ли привод прочесть ее содержимое или нет?
Шаг второй. Получение образа оригинального диска.
Запускаем программу Clone CDCloneCD и указываем ей создать образ оригинального диска (выбираемый профиль настроек на данном этапе некритичен, поскольку диск еще не защищен, то с равным успехом можно использовать как "CD с данными", так и "Protected PC Game"; флажок "создавать Cue-Sheet" устанавливать необязательно –— все равно он действителен лишь на односессионных компакт-дисках).
Шаг третий. Искажение стартового адреса первого трека в образе. Если все сделано правильно и программно/аппаратное обеспечение во всей своей совокупности работает нормально, на жестком диске должны образоваться три файла: IMAGE.CCD, –— несущий в себе содержимое Q-канала подкода области Lead-inLead-In или, попросту говоря, TOC; IMAGE.IMG –— "сырой" образ диска со всеми секторами от 00:00:02 до "сколько -на -диске -есть -там" и IMAGE.SUB –— содержимое полей подкода "программной" части диска.
Могущество кодов Рида-Соломона или информация, воскресшая из пепла
Энтропия слепа, но терпелива. Рано или поздно, обстреливая наши позиции по квадратам, она нанесет удар по штабу, по центру связи. И тогда первая линия обороны будет уничтожена. И приходится отходить на запасные позиции. Иными словами, доставать из магнитотеки пакет дисков с копией тома.
Е. В. Лишак
"Тридцать второй день года.
(Записки парасистемного программиста)."
Все вы наверняка слышали о существовании помехозащитных кодов Рида-Соломона, широко использующихся в устройствах передачи и хранения данных для обнаружения и исправления как одиночных, так и групповых (!) ошибок. Область их применения необычайно широка — кодеры/декодеры Рида-Соломона можно найти и в ленточных запоминающих устройствах, и контроллерах оперативной памяти, и в модемах, и в жестких дисках, и в приводах CD-ROM/DVD приводах и т. д. Благодаря им некоторые "продвинутые" архиваторы безболезненно переносят порчу нескольких секторов носителя, содержащего архив, а подчас — и полное разрушение целого тома многотомного архива. Еще коды Рида-Соломона позволяют защитному механизму автоматически восстанавливать байтики, "хакнутые" взломщиком и/или искаженные в результате сбоя программного/аппаратного обеспечения.
Короче говоря, если владение техникой помехозащитного кодирования не превращает вас в бога, то, по крайней мере, поднимает на Олимп, где среди бесшумных вентиляторов и "безглючных" операционных систем снуют великие компьютерные Гуру.
В тоже время, лишь немногие программисты могут похвастаться собственной реализацией алгоритмов Рида-Соломона. Да и зачем? Готовых библиотек море — от прагматичных коммерческих пакетов, до бесплатных "исходников", распространяемых по лицензии GNU[n2k56] [Y57] . Как говориться, бери — не хочу.
Замечание
"…из-за ошибок в реализации данный код вместо исправления ошибок добавляет новые. Поэтому данный код больше недоступен" — комментарий к исходным текстам GNU'шным[n2k58] исходным текстам кодера/декодера Reed-Solomon'a'а GNU.
Вот и верь после этого в надежность Linux в целом и в "GNU-'тый" библиотечный код в частности.
Что ж, в использовании библиотек есть вполне определенный практический смысл, но никакой хакер (hacker) [n2k59] не доверит управления программе, до тех пор не поймет как именно она работает (а эта публикация именно для хакеров и предназначена, естественно "хакеров" в хорошем значении этого слова). С другой стороны, — при анализе программного обеспечения, распространяемого без исходных кодов, вы не сможете идентифицировать алгоритм Рида-Соломона, если только заранее не разберетесь во всех его тонкостях. Допустим, вам встретилась защита, хитрым образом манипулирующая с EDC/ECC полями ключевых секторов, считанных ею с лазерного диска, и каждый такой сектор содержит две умышленно внесенные ошибки (плюс еще ошибки, естественным путем возникающие при небрежном обращении с CD), причем, одна из этих ошибок ложная
и исправлять ее не нужно. При штатном копировании защищенного диска микропроцессорная начинка привода CD-ROM'a'а автоматически исправляет все ошибки, которые она только может исправить, в результате чего происходит искажение ключевых меток и, — как следствие — защищенная программа перестанет работать. Можно, конечно, скопировать диск в "сыром" режиме, т. е. без исправления ошибок, но тогда копия будет содержать как не предумышленные, так и предумышленные ошибки, в результате чего даже при незначительном повреждении оригинала, корректирующих возможностей кодов Рида-Соломона уже окажется недостаточно и диск просто перестанет читаться (А как вы хотели? Копирование дисков в сыром режиме ведет к накоплению
ошибок и потому крайне непрактично с любой точки зрения). Владение базовыми принципами помехозащитного кодирования позволит вам разобраться с логикой работы защитного механизма и понять какие конкретного ошибки следует исправлять, а какие нет.
К сожалению, подавляющее большинство публикаций на тему кодов Рида-Соломона написаны на языке высшей математики для постижения которой и университетских знаний под час оказывается недостаточно (да и все ли хакеры знают математику?), в результате чего все эти сильно теореитиизированные руководства забрасываются на полку, если вообще не применяются не по назначению…отправляются в туалет (не то, чтобы я разделял такие намерения, но все-таки…).
Программная реализация корректирующих кодов Рида-Соломона действительно очень сложна и действительнона самом деле требует определенной математической подготовки, изложение основ которой может показаться скучным и неинтересным для "системщиков" и "железячников", но иного пути по- видимому нет. В конце концов, никто не обещал вам, что быть программистом — легко, а хорошим программистом быть еще труднее. Так что не говорите потом, что я вас не предупреждал! Шутка! РасслабитесьРасслабьтесь и разгоните свой страх перед высшей математикой прочь. По ходу описания вам встретьсяповстречается пара формул (ну куда же в математике без формул?), но во всех остальных случаях я буду говорить на интернациональном программистом языке — языке Си, понятным любому системщику. В общем, пристегивайте ремни и поднимайте свои головы с клавиатуры, — мы поехали!
Мысли о хакерах, защитах и программировании
Взломщики и защитники информации не только враги, но и коллеги. Если предположить, что хакеры паразитируют на программистах (пользуясь их неумением строить по настоящему качественные защитные механизмы), то тогда с неизбежностью придется признать, что программисты паразитируют на пользователях, пользуясь их неумениеним программировать!
Хакерство и программирование действительно очень тесно переплетены. Создание качественных и надежных защитных механизмов требует навыков низкоуровневой работы с операционной системой, драйверами и оборудованием; знаний архитектуры современных процессоров и учета особенностей кодогенерации конкретных компиляторов, помноженных на "биологию" используемых библиотек. На этом уровне программирования грань между собственно самими программированием и хакерством становится настолько зыбкой и неустойчивой, что я не рисковал бы ее провести.
Начнем с того, что всякая защита, равно как и любой другой компонент программного обеспечения, требует тщательного и всестороннего тестирования на предмет выяснения ее работоспособности. Под "работоспособностью" в данном контексте поднимается способность защиты противостоять квалифицированным пользователям, вооруженным хакерским арсеналом (копировщиками защищенных дисков, эмуляторами виртуальных приводов, оконными шпионами и шпионами сообщений, файловыми мониторами и мониторами реестра). Качество защиты определяется отнюдь не ее стойкостью, но соотношением трудоемкости реализации защиты к трудоемкости ее взлома. В конечном счете, взломать можно любую защиту –— это только вопрос времени, денег, квалификации взломщика и усилий, но грамотно реализованная защита не должна оставлять легких путей для своего взлома. Конкретный пример. Защита, привязывающая к сбойным секторам (которые действительно уникальны для каждого носителя) бесполезна, если не способна распознать их грубую эмуляцию некорректно заполненными полями EDC/ECC[2][n2k11] . Еще более конкретный пример. Привязка к геометрии спиральной дорожки лазерного диска даже будучи реализованной без ошибок, обходится путем создания виртуального CD-ROM привода, имитирующего все особенности структуры оригинального диска.
Для этого даже не нужно быть хакером, –—
достаточно запустить копировщик Alcohol 120%, ломающий такие защиты автоматически.
Ошибки проектирования защитных механизмов очень дорого обходятся их разработчикам, но гарантированно застраховаться от подобных просчетов –— невозможно. Попытка применения "научных" подходов к защите программного обеспечения –—
чистейшей воды фарс и бессмыслица. Хакеры смеются над академическими разработками в стиле "расчет траектории сферического коня в вакууме" и, практически любая такая защита "снимается" за 15
минут без напряжения извилин. Вот грубый, но наглядный пример. Проектирование оборонной системы военной крепости без учета существования летательных средств позволяет захватить эту самую крепость чуть ли не на простом "кукурузнике" (MS WDB [Y12] [n2k13] –—
кукурузник), не говоря уже об истребителях (отладчик [n2k14] Soft-Ice –—
истребитель, а дизассемблер IDA Pro –—
еще и бомбардировщик).
Для разработки защитных механизмов следует иметь хотя бы общее представления о методах работы и техническом арсенале противника, а еще лучше –— владеть этим арсеналом не хуже противника (то есть владеть им в совершенстве). Наличие боевого опыта (реально взломанных программ) очень и очень желательно, –— пребывание в "шкуре" взломщика позволяет досконально изучить тактику и стратегию наступательной стороны, давая тем самым возможность оптимальным образом сбалансировать оборону. Попросту говоря, определить и усилить направления наиболее вероятного вторжения хакеров, сосредоточив здесь максимум своих интеллектуальных сил. А это значит, что разработчик защиты должен глубоко проникнуться психологией хакеров, настолько глубоко, чтобы начать мыслить как хакер…
Таким образом, владение технологией защиты информации предполагает владение технологией взлома. Не зная того, как ломаются защиты, не зная их слабых сторон, не зная арсенала хакеров –— невозможно создать стойкую, дешевую и главное простую в реализации защиту.Книги, рассматривающие вопросы безопасности исключительно со стороны защиты, грешат тем же, что и конструкторы запоминающих устройств, работающих только на запись, –— ни то, ни другое не имеет никакого практического применения.
Нанесение меток vs[8]. динамическая привязка
Умышленное нанесение уникальных меток на носитель само по себе не сложно и широко распространено. Кто победнее царапает диск циркулем, ну а кто побогаче уродует его лазером. Более изощренные защитники программ прибегают к питам нестандартной формы или сложным образом манипулируют с плотностью спиральной дорожки и/или ее узором. Однако все эти способы не лишены недостатков. Во-первых, они требуют применения специального оборудования. Во-вторых, уникальные характеристики носителя потому и называется уникальными, что не могут быть "состряпаны" по заказу и формируются в процессе рождения самого носителя. То есть, до того момента, пока диск не будет вынут из "печки", защитный механизм еще не знает тех характеристик носителя, к которым он привязывается. А после завершения процесса "выпечки" сообщать эти самые характеристики защите уже поздно, т. к. записывать их уже некуда, — дозапись на носители CD-ROM, увы, не возможна. Теоретически, можно поместить закодированные метки на дискету, прилагающуюся к защищенному диску CD-ROM, но ведь это маразм. В, четвертых, те "уникальные" характеристики, которые наносятся на мастер копию CD-ROM, оказываются бессильными против тех "нечистых на руку" заводов по штамповке, что часть отпечатанного тиража "сливают" в свой карман.
Поэтому, лучше вообще не наносить на диск никаких меток, а использовать те, что уже есть, определяя их уникальность "на лету". Как это можно сделать? Да очень просто! Защитный механизм измеряет ту характеристику диска, что подвержена наибольшему разбросу от одного экземпляра носителя к другому (как правило это временная характеристика чтения). Затем по специально заданному алгоритму защитный механизм преобразует ее в некоторый код, который и сообщается владельцу этого диска. Владелец передает этот код разработку программы и получает (не бесплатно, конечно) регистрационный номер, который представляет собой некоторую производную от этого кода (для простоты будет считать, что регистрационный номер равен коду характеристики диска, умноженному на 0x666).
После ввода регистрационного номера, защитный механизм, проделывает с ним обратную операцию, а затем сравнивает полученный результат с кодом характеристики диска (как вариант: защита может самостоятельно вычислить регистрационный номер по коду характеристики и сравнить его с регистрационным номером, введенным пользователем). Если они совпадают, то все ОК, в противном же случае пользователь посылается туда, куда Макар телят не гонял.
Достоинство этого механизма в том, что для создания защищенного диска не обязательно иметь никакого специфического оборудования, — вполне подойдет обычный пишущий привод. Это раз. Копирование защищенного диска протекает без каких-либо осложнений, однако, все дубликаты автоматически теряют свой регистрационный статус (ведь их код характеристики будет уже другой!), но могут быть зарегистрированы путем обращения к разработчику программы — это два!
Естественно, алгоритм генерации регистрационного номера должен быть выбран так, чтобы между ним и кодом характеристик не виделосьобнаружилось никакой видимой никакой зависимости, а сама процедура его проверки всячески сопротивлялась ее исследованию дизассемблером или отладчиком. В противном случае, вашу защиту смогут легко и эффективно вскрыть.
Некорректный номер последнего трека
Искажение номера последнего трека (указатель A1h в TOC) подавляющее большинство приводов воспринимает крайне "болезненно", поэтому данный способ защиты носит скорее академический, чем практических характер (по типу: так защищать свои диски не надо). Тем не менее, при условии соблюдения определенных предосторожностей и пользоваться этой методикой все-таки можно.
Итак, берем уже известный нам образ диска IMAGE.CCD, находим в нем строку "Point=0xA1", расположенную в сессии1 и изменяем значение принадлежащего ей поля PMin с единицы на двойку. Тем самым мы заставляем привод считать, что номер последнего трека первой сессии равен двум, а не одному, как это имеет место быть в действительности.
[Entry 1] [Entry 1]
Session=1 Session=1
Point=0xa1 Point=0xa1
… …
PMin=1 PMin=2
PSec=0 PSec=0
PFrame=0 PFrame=0
При записи измененного образа на диск Clone CDCloneCD выдает вполне корректную информацию о количестве треков, косвенно свидетельствуя о том, что он вообще не анализирует значения указателя A1h, а номер последнего трека определяет путем анализа указателей 01h— – 99h. Последний встретившийся указательpointer – и будет последним номером трека данной сессии.
(Внимание!:
point Указатель с наибольшим номером не обязательно является последним треком, т. к. нумерация треков может быть умышлено искажена с целью затруднения копирования диска).
Защищенный таким образом диск (точнее и, правильнее было бы сказать "изуродованный таким образом диск") под приводом ASUS показывает лишь свою первую сессию. NEC "видит" оба трека, но ввиду "разбушевавшейся фантазии своих электронных цепей" ошибочно распознает их тип как AUDIO, однако, проигрывать их наотрез отказывается (жаль, а то бы мы услышали неповторимую симфонию Шума и Скрежета).
Привод TEAC не опознает такой диск вообще. Короче, все указывает на то, что микропроцессорная начинка приводов ведет себя совсем не так, как Clone CDCloneCD и вместо подсчета количества треков, напрямую считывает содержимое указателя A1h. И, если он оказывается искажен, поведение привода становится в высшей степени неадекватно. В общем, это плохая защита и мы ее рассматривать не будем.
Гораздо "лояльнее" приводы относятся к искажению номера последнего трека второй сессии (в том смысле, что "изуродованная" вторая сессия никак не мешает чтению первой). Давайте в порядке свободного эксперимента возьмем оригинальную копию IMAGE.CCD и совершим над ней следующую "хирургическую" операцию (изменим содержимое поля PMin, принадлежащего point'у указателю A1h второй сессии с двойки на единицу, пытаясь убедить привод в том, что номер последнего трека второй сессии оканчивается на единицу, хотя можно выбрать и любое другое значение, например три или восемь)::
[Entry 8] [Entry 8]
Session=2 Session=2
Point=0xa1 Point=0xa1
… …
PMin=2 PMin=1
PSec=0 PSec=0
PFrame=0 PFrame=0
Приводы ASUS и TEAC показывает лишь первую сессию искаженного диска, вторя же недоступна даже не секторном уровне. Привод NEC так же видит только одну сессию —– первую, но "великодушно" позволяет считывать вторую на секторном уровне, тем не менее на щедрость подобного рода нам закладываться нельзя, поскольку подавляющее большинство остальных приводов не столь "благородны", как NEC.
Попробуем скопировать защищенный диск, предварительно изучив его геометрию с помощью любой подходящей программы (например, Ahead Nero).
Смотрите (см. рис. 6.190x070) Ahead Nero Нерон видит оба трека диска, но второй —– искаженный —– трек представляется ему абсолютно пустым, что в общем-то недалеко от истины, т. к. на секторном уровне этот трек все равно не доступен. Попытка копирования диска тем же Ahead Nero Нероном приводит к копированию лишь первой его сессии и хотя скопированный диск формально вполне работоспособен, защита может разгадать подмену элементарным анализом TOC, проверяя количество сессий, имеющихся на диске и атрибуты второго трека. На диске, скопированным Ahead Nero Нероном они, разумеется, не совпадут.
Рис. 6.19. унок 14 0x070 Реакция Ahead Nero Нерона на некорректный номер последнего трека
Копировщик Clone CDCloneCD так же "обламывается" с копированием. Во-первых, он теряет способность корректно распознавать границы сессии и при достижении конца первой сессии упорно пытается прочесть содержимое области Lead-outLead-Out на секторном уровне. Правда, процесс "обмолачивания" дефективных секторов протекает достаточно быстро и пользователь даже не успевает заскучать. Вторая сессия по понятным причинам остается не скопированной и Clone CDCloneCD корректирует TOC, выбрасывая из него всякие упоминая о последней (это во-вторых). Как следствие: TOC скопированного диска будет кардинально отличается от TOC'a оригинала и защите не составит большого труда обнаружить факт несанкционированного копирования. Alcohol 120% Алкоголь
так же копирует только первую сессию.
Может ли такой диск быть скопирован вручную? Разумеется! Достаточно только один к одному переписать оригинальный TOC не внося в него никаких изменений и "залить" на диск содержимое первой сессии.
Некорректный Run-out как средство защиты или X-?сектор
С хакерской точки зрения программа StompRecord NOW! интереса уже хотя бы тем, что это практически единственная сервисная программа, скрытно записывающая на каждый "прожигаемый" диск специальную метку, —– своеобразный "водяной знак", о существовании которого подавляющее большинство пользователей даже и не догадывается и который со всей очевидностью нарушает их privacy [Y184] [n2k185] (). Но все по порядку.
Поддержка произвольной записи (Randomly Writable) в CD-RW дисках реализована через посредствам механизма блоков вбега/выбега (run-in/run-out block), тесно связанных с режимом пакетной записи. Каждый пакет (packet) начинается с четырех вбегающих блоков (трех —– на носителях DDCD носителях) и завершается двумя блоками выбегам (тремя —– на носителях DDCD носителях) (рис. 6.24). Блоки вбега/выбега представляют собой обыкновенные сектора, но с необычным значением поля MODE в их заголовке (листинг 6.62см. таблицу xxx). В зависимости от режима адресации блокиа вбега/выбега либо адресуются точно так же как и все остальные сектора, либо же исключаются из адресного пространства. Вне режима пакетной записи блоки Run-in/Run-out блоки практически нигде не используются, однако…
Рис. 6.24. унок 19 0x113 Области вбега и выбега
Листинг 6.62. Расширенная интерпретация поля MODE
Bits 7, 6, 5 = 000 - User Data block
= 001 - Fourth Run-in block
= 010 - Third Run-in block
= 011 - Second Run-in block
= 100 - First Run-in block
= 101 - Link block. Physical linking of EFM data
= 110 - Second Run-out block
= 111 - First Run-out block
Bits 4, 3, 2 = 000 - Reserved
Bits 1, 0 = 00 Mode 0 Data
= 01 - Mode 1 Data
= 10 - Mode 2 Data
= 11 -– Reserved
Рисунок 20 Расширенная интерпретация поля MODE
…интересным свойством программы Stomp Record NOW! является ее неявная поддержка блоков Run-out блоков, скрыто внедряемых в конец каждого трека данных, точнее —– в предпоследний сектор его области пост-зазораной области (Post-gap area), заголовок которого подвергается незначительным искажениям.
Поскольку, выбегающий блок всего один (тогда так по " Оранжевой книге" их должно быть как минимум два), да и вбегающих блоков не наблюдается, речь идет именно об искажении. Случайным или непреднамеренном – неизвестно. Возможно, это своеобразный "фирменный знак" или "водяная метка" разработчиков, так же иногда называемая "пасхальным яйцом", затрудняющая копирование оригинального диска (в рамках данной главы под "оригинальным диском" мы будем понимать диск, созданный с помощью Stomp Record NOW!, а предпоследний сектор области пост-зазораной области всякого трека —– "водяным" сектором или X-сектором).
Поле MODE X-сектора, специфицирующее тип данного трека, вместо действительного трека замещается "водяной" константой E1h (реже —– E2h), что соответствует квалификатору первого блока выбега (см. листинг 6.62таблицу ххх). Три старших бита определяют конкретный квалификатор блока, а два младших —– тип трека. Таким образом, "водяное" число в общем случае равно: Water Mark Value (WMV) == E0h | Track MODE, соответственно: Track MODE == MODE & 3, где Track MODE —– тип трека, а MODE —– значение соответствующего поля заголовка сектора.
Помимо этого, в область данных X-сектора заносится идентификационная строка и серийный номер привода, осуществляющего "прожиг" диска (если только данный рекордер "знаком" Stomp Record NOW!).
"Желтая книга", так же известная под именем ECMA-130 (базовый стандарт лазерных дисков с данными), допускала существование лишь трех типов треков: MODE 0, MODE 1 и MODE 2, а все остальные трактовала как ошибку. Приложения, спроектированные в полном соответствии со стандартом ECMA-130, не в состоянии определить действительны тип X-?сектора, поскольку не "знают", что шесть старших бит поля MODE должны быть обнулены.
Такой сектор и в корзину не выкинешь, и через декодер Рида- Соломона не пропустишь, поскольку заранее неизвестно: присутствуют ли в нем коды EDC/ECC коды или же их там нет.
По стандарту области пред-зазора и пост-зазора предназначены исключительно для позиционирования оптической головки[7]
и не содержат в себе пользовательских данных. При штатной работе привода к этим секторам вообще не происходит никаких обращений (стандарт позволяет закладываться лишь на данные субканалов), благодаря чему "водяные знаки" (равно как и блоки вбега/выбега) никак не сказываются на работе привода и последний их просто не замечает. В тоже самое время, содержимое областей пред- и пос-зазора активно используется различными защитными механизмами для хранения ключевых меток, работающих приблизительно по такому же принципу, что и ключевые метки, записанные в инженерные сектора жестких дисков (71 и/или 72 трек дискеты). Не исключено, что между "водяными знаками" и ключевыми метками возникнет непредвиденный конфликт, приводящий к полной или частичной неработоспособности одной из сторон, так что злоупотреблять данной техникой право же не стоит.
Чтобы узнать, присутствуют ли на исследуемом диске "водяные знаки" или нет, мы должны считать предпоследние сектора всех Post-gap'ов в "сыром" виде, для чего нам пригодится утилита CD_RAW_SECTOR_READ или любая другая, аналогичная ей (очень хорошо подходит для этой цели Clone CDCloneCD). Остается определить абсолютные адреса конца областей пост-зазора всех треков. Это легко. Абсолютный адрес последнего сектора области Post-gap области равен стартовому адресу следующего трека минус sizeof(pre-gap) (стартовому адресу выводной области, если этот трек —– последний) минус единица. Соответственно, чтобы выйти на след предпоследнего сектора, от полученное значение следует уменьшить на единицу. Другими словами: x-sector address = (next track != lead-out)?next track address – 3:lead out address – 2.
Стартовые адреса всех треков хранятся в point'ах указателях с номерами от 01 до 99 включительно, а стартовый адрес выводной области диска —– в point'e указателе с номером A2h. Допустим, на исследуемом диске имеется всего один-единственный трек и стартовый адрес выводной области равен 00:29:33
(см. листинг 6.63 ниже), тогда X-сектор будет располагаться по адресу 00:29:31.
Листинг 6.63. Определение адреса выводной области
[Entry 2]
Session=1
Point=0xa2
ADR=0x01
Control=0x04
TrackNo=0
AMin=0
ASec=0
AFrame=0
ALBA=-150
Zero=0
PMin=0
PSec=29
PFrame=33
PLBA=2058
Листинг 54 определение адреса выводной области
Отметим, что абсолютному адресу 00:29:31 соответствует LBA-адрес равный 2056. Запомним это значение, так как оно не раз и не два встретится в наших дальнейших экспериментах. Передав полученный адрес программе CD_RAW_SECTOR_READ, мы через секунду-другую получим на выходе его содержимое. Найти "водяной" сектор" в образе диска, снятом с помощью Clone CDCloneCD (или любой другой аналогичной ей программы) несколько сложение, но все же возможно. Существует по меньшей мере два пути: зная размер одного "сырого" сектора (2352 байта) и LBA-адрес "водяного" сектора, мы можем вычислить смещение искомого сектора в файле простым перемножением обоих величин (2352 * 2056 == 49С980h). Другой путь: просто поискать контекстным поиском HEX-последовательность 00 FF FF FF FF FF FF FF FF FF FF 00 00 29 31, т. е. Sync + address.
Но каким бы путем мы ни шли, результат будет таким как показано в листинге 6.64 (некорректный номер трека в заголовке сектора — E1h вместо 01h — выделен полужирным шрифтом и взят в рамку — и идентификатор рекордера, на котором выполнялся "прожиг" диска также выделен полужирным шрифтом)ов:
Листинг 6.64. "Водяные знаки", внедренные в предпоследний сектор области Post-gap программой Stomp Record Now!
0049C980: 00 FF FF FF FF FF FF FF ¦ FF FF FF 00 00 29 31 E1 )1с
0049C990: 52 49 44 30 31 00 00 00 ¦ 4E 45 43 00 00 00 00 00 RID01 NEC
0049C9A0: 4E 52 31 31 00 00 00 00 ¦ 02 58 56 00 00 00 00 00 NR11 OXV
0049C9B0: 4E 45 43 20 20 20 20 20 ¦ 20 20 20 20 20 20 20 20 NEC
0049C9C0: 20 20 20 20 20 20 20 20 ¦ 20 20 20 20 20 20 20 20
0049C9D0: 4E 52 2D 39 31 30 30 41 ¦ 20 20 20 20 20 20 20 20 NR-9100A
0049C9E0: 32 58 56 32 32 38 31 53 ¦ 31 31 31 20 20 20 20 20 2XV2281S111
0049C9F0: 00 00 00 00 00 00 00 00 ¦ 00 00 00 00 00 00 00 00
0049CA00: 00 00 00 00 00 00 00 00 ¦ 00 00 00 00 00 00 00 00
Листинг 55 водяные знаки, внедренные в предпоследний сектор post-gap'a программой Stomp Record Now: некорректный номер трека в заголовке сектора (E1h вместо 01h – выделен жирным шрифтом и взят в рамку) и идентификатор рекордера, на котором выполнялся прожиг диска (выделен жирным шрифтом)
Штатные копировщики (и Ahead Nero в частности) не копируют содержимое областей пред- и пост-зазора и потому "водяные знаки" на скопированных дисках просто отсутствуют! Чтобы отличить оригинальный диск от его копии защитный механизм должен: 1) а) используя команду READ TOC (format 0x2 —– full TOC), считать оглавление диска в "сыром" виде; 2б) считать адрес выводной области любой из сессий (например, первой из них); 3в) определить адрес "водяного" сектора и, используя команду READ CD, считать его в "сыром" виде; 4г) проанализировать значение поля MODE (пятнадцатый байт заголовка сектора, считая от нуля),; если это поле больше двух, то – мы имеем дело с оригиналом или его качественной копией; . 5д) параноикам можно порекомендовать считать содержимое пользовательской области данных и сличить его с эталоном. Это не добавит защите стойкости (если копировщик скопирует "водяной" маркер в поле MODE, то он скопирует и пользовательскую часть сектора), но, возможно, придаст его разработчику чувство самоуспокоения.
Простейший пример реализации защитного механизма выглядит так как показано в листинге 6.65 (конечно, условный переход, осуществляющий сравнение поля MODE с эталонной "водяной" константой очень легко обнаружить и "отломать", поэтому для усиления стойкости защиты следует отказаться от явных проверок и использовать считанный заголовок сектора, например, для расшифровки критических участков кода).:
Листинг 6.65. [crackme. 68E8B0Abh] поиск "водяных знаков", оставленных программой Stomp Record NOW!
// ВАЖНЕЙШИЕ КОНСТАНТЫ
#define _WATERMARK 0xE1 // код водяного знака
#define _A2 3 // смещение point'a A2h в TOC'e
#define _MODE 15 // смещение поля MODE в заголовке сектора
#define _M 8 // смещение поля PMin в TOC'e
#define _S 9 // смещение поля PSec в TOC'e
#define _F 10 // смещение поля PFrame в TOC'e
#define argCD argv[1]
main(int argc, char** argv)
{
int a, b, x_sec, LBA_lead_out = 0;
unsigned char buf[RAW_SECTOR_SIZE*2];
// TITLE
fprintf(stderr,"crackme.68E8B0ABh Record NOW! watermark\n");
// справка по ключам
if (argc != 2) {
printf("USAGE: crackme.68E8B0ABh.exe CD\n"); return -1;}
// читаем TOC в сыром виде
a = cd_raw_toc_read(argCD, buf, RAW_SECTOR_SIZE, W_FULL_TOC);
if (a != SCSI_OK) { // операция выполнена успешно?
fprintf(stderr, "-ERR: read TOC\x7\n"); return -1; }
// поиск point'a A2h, хранящего стартовый адрес выводной сессии
for (a = 4; a < buf[0]*0x100L+buf[1]; a+=11)
{
// это point A2?
if (buf[a + _A2] == 0xA2)
{
// point A2 найден
// получаем адрес выводной области первой сессии и сваливаем
LBA_lead_out=((buf[a+_M]*60+buf[a+_S])*75+buf[a+_F])-150; break;
}
}
// поиск адреса выводной области прошел успешно?
if (LBA_lead_out == 0) {
fprintf(stderr,"-ERR: find A2h point\x7\n"); return -1;}
// вычисляем адрес x-сектора, хранящего водяной знак
x_sec = LBA_lead_out - 2;
// читаем сектор с водяным знаком в сыром виде
a = cd_raw_sector_read(argCD, buf, RAW_SECTOR_SIZE*2, x_sec, 1, 0xF8);
if (a != SCSI_OK) { // чтение сектора прошло успешно?
fprintf(stderr, "-ERR: read x-sector\x7\n"); return -1; }
// проверка на наличие водяного знака
if (buf[_MODE] != _WATERMARK)
{
// это не оригинальный диск
fprintf(stderr, "hello, hacker!\x7\n"); return 0;
}
// это оригинальный диск
printf("hello, legal user!\n");
}
Листинг 56 [crackme. 68E8B0Abh] поиск водяных знаков, оставленных программой Stomp Record NOW!
Тестирование копировщиков защищенных лазерных дисков показывает, что "водяные знаки" не копируются Clone CDCloneCD, который наотрез отказывается обрабатывать такие "неправильные" (с его точки зрения!) сектора, молчаливо приводя их в более потребный вид —– автоматически корректирует значение поля MODE и уничтожает всю идентификационнуюю информацию, в результате чего скопированный сектор приобретает следующий вид (листинг 6.66).:
Листинг 6.66. На копии диска, полученной CloneCD, "водяные знаки" бесследно исчезают и поле MODE нормализуется! Таким образом, защищаемая программа может легко отличить оригинальный диск от его пиратского дубликата
0049C980: 00 FF FF FF FF FF FF FF ¦ FF FF FF 00 00 29 31 01 ¦$ O
0049C990: 00 00 00 00 00 00 00 00 ¦ 00 00 00 00 00 00 00 00
0049C9A0: 00 00 00 00 00 00 00 00 ¦ 00 00 00 00 00 00 00 00
0049C9B0: 00 00 00 00 00 00 00 00 ¦ 00 00 00 00 00 00 00 00
0049C9C0: 00 00 00 00 00 00 00 00 ¦ 00 00 00 00 00 00 00 00
0049C9D0: 00 00 00 00 00 00 00 00 ¦ 00 00 00 00 00 00 00 00
0049C9E0: 00 00 00 00 00 00 00 00 ¦ 00 00 00 00 00 00 00 00
Листинг 57 на копии диска, полученной Clone CD, водяные знаки бесследно исчезают и поле MODE нормализуется! таким образом, защищаемая программа может легко отличить оригинальный диск от его пиратского дубликата.
В других случаях скопированный X-сектор вообще не читается и команда READ CD с завидным постоянством возвращает малоинформативную сообщение от ошибке типа "MEDIUM ERROR" (ошибка носителя). Причины этого до конца не ясны. Возможно, Clone CDCloneCD пытается таким образом эмулировать bad-сектор, ошибочно посчитав X-сектор плохим, возможно это следствие неправильного обращения с пишущим приводом"писцом" (если Track MODE > 2 то скремблирование сектора, записываемого в "сыром" режиме по идее не выполняется и на диске образуются неблагоприятные регулярные последовательности, которые в силу определенных конструктивных ограничений привода оказывается не так-то просто прочитать).
Но так или иначе, все "водяные знаки" копировщик Clone CDCloneCD просто "съедает", и защищенный диск оказывается неработоспособным. Конечно, в следующих версиях Clone CDCloneCD ситуация может круто измениться (поддержка X-секторов не требует кардинальных переработок кода копировщика и может появиться в любое время) и тогда защита, основанная на "водяных" метках, неизбежно падаетстановится бесполезной, кроме того X-сектора успешно копируются Alcohol 120% Алкоголем и вроде бы CDRWin, так что в чистом виде "водяные знаки" недостаточно эффективны и для усиления защиты их следует комбинировать с защитами других типов (искаженный стартовый адрес первого трека, нулевой трек на диске, фиктивный трек в Post-gap ключевого трека и т. д.).Подобные аддитивные защиты чрезвычайно стойки к копированию и на сегодняшний день не копируются ничем.
Некорректный стартовыйого номера первого трека
Искажение стартового номера первого трека—– достаточно честный и стойкий прием защиты. Подавляющее большинство приводов вполне уверенно "заглатывают" диски, нумерация треков которых начинается с цифры отличной от единицы. Предположим, что диск начинается с трека номер два…
Вернемся к оригинальному образу защищаемого диска и отредактируем файл IMAGE.CDD следующим образом (короче говоря, мы изменим сдвинем номера всех треков на единицу, не забывая о том, что номер первого и последнего трека каждой сессии хранится в pointer'ах указателях 0xA0 и 0xA1 соответственно и для корректной защиты диска они так же должны быть модифицированы) (листинг 6.29—6.31). Серой заливкой выделены оригинальные значения, без заливки — измененные. Непосредственно сами изменения отмечены стрелкой и полужирным шрифтом.:
Листинг 6.29. Изменение номера первого трека
[Entry 0] | [Entry 0] | [Entry 1] | [Entry 1] | [Entry 3] | [Entry 3] | ||||||
Session=1 | Session=1 | Session=1 | Session=1 | Session=1 | Session=1 | ||||||
Point=0xa0 | Point=0xa0 | Point=0xa1 | Point=0xa1 | Point=0x1 Þ | Point=0x2 | ||||||
… | … | … | … | … | … | ||||||
PMin=1 Þ | PMin=2 | PMin=1 Þ | PMin=2 | PMin=0 | PMin=0 | ||||||
PSec=0 | PSec=0 | PSec=0 | PSec=0 | PSec=2 | PSec=2 | ||||||
PFrame=0 | PFrame=0 | PFrame=0 | PFrame=0 | PFrame=0 | PFrame=0 |
Листинг 6.30. Изменение номера второго трека
[Entry 7] | [Entry 7] | [Entry 8] | [Entry 8] | [Entry 10] | [Entry 10] | ||||||
Session=2 | Session=2 | Session=2 | Session=2 | Session=2 | Session=2 | ||||||
Point=0xa0 | Point=0xa0 | Point=0xa1 | Point=0xa1 | Point=0x2 Þ | Point=0x3 | ||||||
… | … | … | … | … | … | ||||||
PMin=2 Þ | PMin=3 | PMin=2 Þ | PMin=3 | PMin=0 | PMin=0 | ||||||
PSec=0 | PSec=0 | PSec=0 | PSec=0 | PSec=2 | PSec=2 | ||||||
PFrame=0 | PFrame=0 | PFrame=0 | PFrame=0 | PFrame=0 | PFrame=0 |
Листинг 6.31. Изменение карты
[TRACK 1]Þ | [TRACK 2] | [TRACK 2]Þ | [TRACK 3] | ||||||||
MODE=1 | MODE=1 | MODE=1 | MODE=1 | ||||||||
INDEX 1=0 | INDEX 1=0 | INDEX 1=0 | INDEX 1=0 |
Обитатели "сумеречной зоны", или из "морга в реанимацию"
Хотите узнать, как заставить приложение продолжить нормальную работу после появления сообщения о критической ошибке? Это действительно очень актуально. Представьте, что "рухнуло" приложение, содержащее уникальные и еще не сохраненные данные. По минимуму их придется набивать заново, по максимуму–— они потеряны для вас навсегда. На рынке имеется некоторое количество утилит, нацеленных на эту задачу (взять те же Norton Utilities), но их интеллектуальность оставляет желать лучшего и в среднем они срабатывают один раз из десяти. В тоже самое время, ручная реанимация программыа воскрешает ее в 75%?90% случаев.
Строго говоря, гарантированно восстановить работоспособность "обрушавшейся" программы нельзя, равно как и невозможно выполнить откат тех действий, что предшествовали ее "обрушению". В лучшем случае вам удастся сохранить свои данные на диск до того, как программа полностью потеряет нить управления и пойдет вразнос. Но и это неплохо!
Существует по меньшей мере три различных способа реанимации:
q а) принудительный выход из функции, возбудившей исключение;
q б) "раскрутка" стека с передачей управления назад;
q в) передача управления на функцию обработки сообщений.
Рассмотрим каждый из этих способов на примере приложения testt.exe, копию которого вы можете ### скачать с нашего сервера[Y89] .
Забегая вперед, отметим, что реанимации поддаются лишь те сбои, которыечто вызваны алгоритмическими, а не аппаратными ошибками (т. е. сбоем оборудования). Если информация, хранящаяся в оперативной памяти, оказалась искажена в результате физического дефекта последней, то восстановить работоспособность "упавшего" приложения скорее всего уже не удастся, хотя если сбой не затронул жизненно важные структуры данных, некоторая надежда на благополучный исход все-таки есть.
>>>>> Общее представление (врезка)
Коды Рида-Соломона представляют собой недвоичные совершенные систематические линейные блочные коды, относящиеся к классу циклических кодов с числовым полем отличным от GF(2) и являющиеся подмножеством кодов Боуза-Чоудхури-Хоквингема[Y60] [n2k61] . Корректирующие способности кодов Рида-Соломона напрямую зависят от количества контрольных байт. Добавление r контрольных байт позволяют обнаруживать r произвольным образом искаженных байт, гарантированно восстанавливая из них r/2 байт из них.
Общие рекомендации по восстановлению
Не всякий не читающийся (не стабильно читающийся) диск –— дефектный. Зачастую в этом виновен отнюдь не сам диск, ано операционная система или привод. Прежде чем делать какие- либо заключения попробуйте прочесть диск на всех доступных вам приводах, установленных на компьютерах с "девственно" чистой операционной системой. Многие приводы, даже вполне фирменные и дорогие (например, мой PHILIPS CD-RW 2400), после непродолжительной эксплуатации становятся крайне "капризными и раздражительными", отказывая в чтении тем дискам, которые все остальные приводы читают безо всяких проблем. А операционная систем по мере обрастания свежим софтом, склонна подхватывать различные "глюки" под час проявляющиеся самым загадочным образом (в частности, привод TEAC установленный в систему с драйвером CDR4_2K.SYS, доставшийся в наследство от привода PHILIPS'a, конфликтует с CD-плеером Player'ом, не соглашаясь отображать содержимое дисков с данными если тот активен, после же удаления CDR4_2K.SYS все идет как по маслу).
Так же не стоит забывать и о том, что корректирующая способность различных моделей приводов очень и очень неодинакова. Как пишет инженер-исследователь фирмы ЕПОС Павел Хлызов в своей статье "Проблема: неисправный CD-ROM": "…в зависимости от выбранной для конкретной модели CD-ROM стратегии коррекции ошибок и, соответственно, сложности процессора и устройства в целом, на практике тот или иной CD-ROM может либо исправлять одну-две мелкие ошибки в кадре информации (что соответствует дешевым моделям), либо в несколько этапов восстанавливать, с вероятностью 99,99%, серьезные и длинные разрушения информации. Как правило, такими корректорами ошибок оснащены дорогостоящие модели CD-ROM. Это и есть ответ на часто задаваемый вопрос: "Почему вот этот диск читается на машине товарища, а мой ПК его даже не видит?".
Вообще-то, не совсем понятно, что конкретно господином инженером-исследователем имелось ввиду: корректирующие коды C1, C2, Q- и P- уровней корректно восстанавливают все известные мне приводы и их корректирующая способность равна: до двух 2 ошибок на каждый из C1 и C2
уровней и до 86- и 52-ошибок на Q- и P- уровни соответственно. Правда, количество обнаруживаемых, но уже математически не исправимых ошибок составляет до 4 ошибок на C1 и C2 уровней и до 172/104 ошибок на Q/P, но… гарантированно определяется лишь позиция сбойных байт во фрейме/секторе, но не их значение. Впрочем, зная позицию сбойных байт и имея с своем распоряжении исходный HF-сигнал (т. е. аналоговый сигнал, снятый непосредственно со считывающей головки), кое-какие крохи информации можно и вытянуть, по крайней мере теоретически… так что приведенная выше цитата в принципе может быть и верна, однако, по наблюдениям автора данной статьи цена привода очень слабо коррелирует с его "читабельной" способностью. Так, относительно дешевые приводы ASUS читают практически все, а дорогие приводы PHILIPS'ы даже свои родные диски с драйверами опознают через раз.
Другая немаловажная характеристика –— доступный диапазон скоростей чтения. В общем случае –— чем ниже скорость вращения диска, тем мягче требования, предъявляемые к его качеству. Правда, зависимость эта не всегда линейна. Большинство приводов имеют одну или несколько наиболее предпочтительных скоростей вращения, на которых их читабельная способность максимальна. Например, на скорости 8x дефектный диск читается на ура, а на всех остальных скоростях (скажем, 2x, 4x, 16x, 32x) –— не читается вообще. Предпочтительная скорость легко определяется экспериментально, необходимо лишь перебрать полный диапазон доступных скоростей.
При покупке привода CD-ROM'a выбирайте тот привод, у которого скоростной диапазон максимален. Например, уже упомянутый выше PHILIPS CDRW 2400 умеет работать лишь на: 16x, 24x, 38x и 42x. Отсутствие скоростей порядка 4x— – 8x ограничивает "рацион" привода только высококачественными дисками.
По непонятным причинам, штатные средства операционной системы Windows не позволяют управлять скоростью диска и потому приходится прибегать к помощи сторонних утилит, на недостаток которых, впрочем, жаловаться не приходится.
Вы можете использовать утилитой Slow CD, Ahead Nero Drive Speed и т. д. Вообще-то, большинство приводов самостоятельно снижают скорость, натолкнувшись на не читающиеся сектора, однако, качество заложенных в них алгоритмов, все еще оставляет желать лучшего и "ручное" управление скоростью дает болеезначительно лучший оптимальный результат.[n2k29]
Если же ни на одном из доступных вам приводов диск все равно не читается, можно попробовать отшлифовать его какой- ни будь полировальной пастой. Технике полирования оптических поверхностей (и лазерных дисков в частности) посвящено огромное количество статей, опубликованных как в печатных изданиях, так и в Интернете (особенно полезны в этом смысле астрономические книги по телескопостроению), поэтому здесь этот вопрос будет рассмотрен лишь кратко. Да, действительно, поцарапанный диск в большинстве случав можно отполировать и если все сделать правильно, диск с высокой степенью вероятности возвратится из небытия, но… Во-?первых, полировка восстанавливает лишь царапины нижней поверхности диска и бессильна противостоять разрушениям отражающего слоя. Во-вторых, устраняя одни царапины, вы неизбежно добавляетевносите другие и после иной полировки лазерному диску может очень сильно "поплохеть". В-третьих, полировке дисков не возможно научиться за раз, –— вам понадобиться уйма времени и куча "подопытных" дисков. Нет уж, благодарю покорно! Лучше мы пойдем другим путем!
А вот что вашему диску действительно не помешает –— так это протирка обычными салфетками, пропитанными антистатиком (ищите их в компьютерных магазинах). Прежде чем вытирать диск сдуйте все частицы пыли, осевшие на него (иначе вы его только больше поцарапаейте) и ни в коем случае не двигайтесь концентрическими мазками! Вытирать поверхность диска следует радиальными движениями от центра к краям, заменяя салфетку на каждом проходе.
Организация CD
В этой, преимущественно теоретической, главе читатель знакомится с организацией CD и принципами оптической записи, без знания которых осмысленная работа с защитными механизмами попросту невозможна.
Физически лазерный диск представляет собой пластинку из поликарбоната с нанесенным поверх нее отражающим алюминиевым (реже— золотым) слоем, защищенным от механических повреждений специальным защитным слоем (см. рис. 1.10х009A). Отражающий слой содержит в себе цепочку углублений или иначе питов (pits) и возвышенностей иначе лендов (lands), свернутую в спираль, —– примерно такую же как на грампластинке (рис. 1.2), но намотанную в обратном порядке: от центра диска к его краям (фактически лазерный диск является устройством последовательного доступа с ускоренной перемоткой).
Рис.унок 11.1. 0х009A/0х013 Лазерный диск в разрезе (слева) и увеличенные питы (справа)
Рис. 1.2. унок 2 0х014 0x2B Лазерный диск это все равно что грампластинка
Ошибки начинающих или то, чего делать не следует
Иногда можно услышать утверждение, что "сграбив" (grab) одну из сессий восстанавливаемого диска в ISO-образ и смонтировав его на виртуальный диск CD-ROM диск (записав на болванку) можно получить доступ к его содержимому. Действуя таким Макаром мы сможет быстро просмотреть содержимое всех необходимых нам сессий.
Так или иначе, доступ к удаленным файлам будет получен и вы сможете делать с ними все, что хотите.
(Внимание!
При просмотре содержимого "сграбленной" сессии всегда учитывайте что: во-первых, файлы, физически принадлежащие другим сессиях, из данной сессии окажутся недоступными, в то время как ссылки на них здесь могут изобиловать. При обращении к реально несуществующему файлу будет выдаваться либо "мусор", либо сообщение об ошибке. Как альтернативный вариант–— операционная система может просто зависнуть. Если это произошло, то просто нажмите кнопку выброса диска "Eject". Windows тут же выйдет из ступора и радостно "завопит" — "устройство не готово". Во-вторых, в силу сквозной адресации секторов, каждая "сграбленная" сессия должна записываеться на тоже самое место диска, на котором она была ранее, в противном случае все ссылки на стартовые адреса файлов внутри этой сессии окажутся недействительными. Требуемый результат обычно достигается изменением стартового адреса первого трека. О том, как это сделать, рассказывается в следующем разделей части главые, посвященномуй восстановлению информации с очищенных CD-RW дисков).
Отрицательный стартовый адрес первого аудио трека
Еще один "извращенный" трюк: установить в TOC'е адрес первого аудио-трека в область, предшествующую Lead-inLead-In'у, то есть, попросту говоря, присвоить этому трекуему отрицательное смещение (см. рис. 7.2 0x023).
Рис. 7.2. унок 22 0х023 Отрицательный стартовый адрес первого трека препятствует проигрыванию диска в PC CD-ROM
Ломается такая защита точно так же, как и предыдущие, поэтому не будемт на ней подробно останавливаться.
">>>>> Отжиг" дисков. За, против и немного вокруг
Может ли Бог сотворить такой камень, который сам же и не сможет поднять –— неизвестно, но вот программист запросто нахкодит такое, что "хрен" потом отладит.
Программистский фольклор
Для защиты дисков от копирования, приступать к созданию своей собственной программы "прожига" совершенно необязательно. Вместо этого вы можете манипулировать с "сырыми" образами дисков, поддерживаемыми программами Алкоголиком Alcohol 120% или Clone CD. Несмотря на то, что все эти программы налагают на записываемые ими образы дисков определенные ограничения, создание качественных защитных механизмов все-таки остается возможным. Другими словами, эти программы спокойно записывают то, что самостоятельно скопировать оказываются не в состоянии!
При создании собственного копировщика защищенных дисков без умения прожигать диски можно, в принципе, и обойтись, –— достаточно лишь подготовить образ диска (т. е. корректно прочитать защищенный диск), ну а тиражирование "хакнутого" образа уже не проблема. Лучше сфокусироваться непосредственно на анализе защищенных дисков, чем изобретать велосипед, в очередной раз разрабатывая то, что уже давно разработано до вас. Alcohol 120%Алкоголь и CloneCD имеют превосходные возможности "прожига", но вот читающий движок у них явно слабоват, и даже незначительные искажения служебных структур лазерного диска способны сбить их столку.
Если же, несмотря ни на что, вы по прежнему убеждены, что свой собственный Нерон, сжигающий Рим, вамс все-таки нужен, что ж! Добро пожаловать в гости к Демону Максвелла, попасть в лапы к которому гораздо сложнее, чем вырваться из них. Шутка! А вся доля правды в том, что техника "прожига" дисков –— чрезвычайно обширный вопрос, даже краткое изложение которого потребовало бы отдельной книги. Одних лишь стандартов и спецификаций по SCSI-командам здесь окажется крайнеболее чем недостаточно, поскольку в них опущены многочисленные подробности процесса генерации различных структур данных, требующихся приводу для корректной записи исходного образа на лазерный диск.
Лучшим из имеющихся пособий по "проотжигу" дисков автор считает приложение "Functional Requirements for CD-R (Informative)" к документу "SCSI-3 Multimedia Commands", электронную версию которого можно найти по адресуздесь: http://www.t10.org/ftp/t10/drafts/mmc/mmc-r10a.pdf
(обратите внимание, что в более поздних ревизиях документа это приложение было изъято).
Попробуйте также обратиться к исходным текстам утилиты CDRTOOLS, которые можно найти по адресуздесь: http://prdownloads.sourceforge.net/cdromtool/cdromtool_2002-11-26.zip?download. Конечно, семь с небольшим мегабайт исходных текстов –— не самое лучшее средство для вдохновения, но более простые программы "прожига" автору неизвестны.
Более трудоемким (но вместе с тем и соблазнительным!) способом является дизассемблирование исполняемых файлов программ Alcohol 120%Алкоголя, Clone CDCloneCD, CDRWin и других программ, включая монстроузного Nero Burning ROMНерона. Собственно, полное дизассемблирование проводить совершенно необязательно, достаточно перехватить передаваемые приводы SCSI-команды и проанализировать последовательность их вызовов, не забывая при этом о значениях аргументов, в которых все ключевые структуры данных, собственно, и содержатся.
В зависимости от способа, выбранного разработчиком приложения для взаимодействия с устройством, "шпионаж" осуществляется либо перехватом функции DeviceIoControl с аргументами IOCTL_SCSI_PASS_THROUGH/IOCTL_SCSI_PASS_THROUGH_DIRECT (4D004h/4D014h), либо SendASPI32Command для интерфейсов SPTI и ASPI интерфейсов соответственно. Приложения, взаимодействующие с приводом посредствомчерез своегой собственногоый драйвера, также поддаются перехвату, но универсальных решений здесь нет, и каждый конкретный случай следует рассматривать индивидуально.
Давайте исследуем копировщик Alcohol 120% Алкоголь на предмет выявления алгоритма очистки и "прожига" CD-RW дисков (CD-R диски "прожигаются" аналогичным образам но, по понятным причинам, не поддаются очистке).
Итак, запускаем программу Alcohol 120%Алкоголика, переходим к вкладке [Y151] [n2k152] "Настройки", щелкаем по ссылке "Общие" и в ниспадающем боксе "Интерфейс управления дисками" выбираем "Интерфейс WinASPI Layer (безопасный режим)", если только он уже не был выбран ранее. После смены интерфейса программа Alcohol 120% Алкоголик потребует перезапуска программы, –— что ж выходим из нееего и тут же запускаем вновь, убеждаясь в работоспособности последнейго.
Теперь вызываем отладчик Soft-Ice (или любой другой отладчик, поддерживающий точки останова на функции API функции) и, предварительно загрузив ASPI-экспорт в память (NuMega Symbol Loader à File à Load Exports à Wnaspi32.dll), открываем процесс Alcohol.exe, при необходимости распаковав (по обыкновению он упакован архиватором [n2k153] упаковщиком исполняемых файлов UPX'ом).
Пытаемся установить точку останова на функции SendASPI32Command, выотдавая отладчику следующую команду "bpx SendASPI32Command", но ничего хорошего из этого у нас не получаетсяполучится, –— архиватор [n2k154] Soft-Ice ругается, что не может найти такую функцию, несмотря на то, что ее имя написано без ошибок. Это не покажется удивительным, если предположить, что библиотека Wnaspi32.dll загружается динамически в ходе выполнения программы и на этапе загрузки Alcohol.exe адреса ASPI-функций еще не известны.
Можно поставить точку останова на функции LoadLibraryA, отслеживая загрузку всех динамических библиотек, но, поскольку программа Alcohol 120% Алкоголь загружает огромное количество разнообразных динамических библиотек (DLL), то на отладку уйдет чудовищное количество времени, в течении которого мы будем "тупо пялиться" на экран, монотонно нажимаявыбивая комбинацию клавиш <CTRL>+<-D> на клавиатуре. Более прогрессивным средством мониторинга будеут установка условной точки останова, которая автоматически отсечет все заведомо ложные вызовы.
Соответствующая ей команда может выглядеть, например, так: "bpx LoadLibraryA IF *(ESP->4) == "SANW"", где SANW –— это четыре первых символа имени "Wnaspi32.dll", записанные задом наперед с учетом регистра, выбранного разработчиком программы (если регистр наперед неизвестен, можно использовать функцию сравнения нечувствительную к регистру).
Затем команда "bpx GetProcAddress" позволит перехватить загрузку всех ASPI-функций и SendASPI32Command в том числе. Имя загружаемой функции может быть просмотрено командой "d esp à 4". Дождавшись появления SendASPI32Command, выдаем командужмем "P RET" и, установив точку останова на "BPX EAX", нажимаем комбинацию клавишьдавим <Ctrl>+<-D> для выхода из отладчика Soft-Ice (все остальные точки останова при желании можно удалить).
По факту "всплытия" отладчика, наскоро набиваемнабираем команду "d esp à 4", и в окне дампа памяти появляется содержимое структуры SRB_ExecSCSICmd. Теперь 30h (48)[n2k155] байт по счету –— это первый байт CDB-пакета (внимание! это именно первый байт пакета, а не указатель на сам пакет); 03h (3) [n2k156] и 10h (72)[n2k157] байты –— это флаги направления передачи данных и указатель на буфер обмена соответственно.
ДалееНиже приведены примеры "шпионских" протоколов, перехваченных в процессе очистки и "прожига" болванки CD-RW болванки (листинг 2.5.2).:
Листинг 2.5.232. Содержимое перехваченных CDB-блоков, посылаемых программой Alcohol 120% Алкоголиком устройству при быстрой очистке лазерного диска
1E 00 00 00 01 00 ß PREVENT REMOVAL (ON) -----------+
51 00 00 00 00 00 ß READ DISK INFORMATION-------+ |
1E 00 00 00 00 00 ß PREVENT REMOVAL (OFF) ------|---+
BB 00 FF FF FF FF ß SET SPEED ----------+ | |
5A 00 2A 00 00 00 ß MODE SENSE -----+ | | |
BB 00 FF FF 02 C2 ß ----------------|---+ | |
5A 00 2A 00 00 00 ß ----------------+ | |
1E 00 00 00 00 00 ß
----------------------------|---+
51 00 00 00 00 00 ß ----------------------------+
A1 11 00 00 00 00 ß
BLANK
Обратите внимание, что для очистки диска программа Alcohol 120% Алкоголь использует SCSI-команду BLANK, подробное описание которой содержится в документах "Multimedia Commands –— 4" и "Information Specification for ATAPI DVD Devices". Именно команда BLANK, а не ERASE, как пытается убедить нас товарищ Всеволод Несвижский в своей книге "Программирование устройств SCSI и IDE". Приведенные им листинги программ не работают, да и не должны работать в принципе. Команда ERASE (10), с кодом операции 2Ch, местами упоминается как команда с кодом операции 1Ch, соответствующим SCSI-команде RECEIVE DIAGNOSTIC RESULTS, кстати, не поддерживаемой оптическими накопителями вообще. Команды ERASE (12) не то чтобы совсем не существовало в природе, однако согласно приведенному авторому коду операции –— ACh –— это есть ни что иное, как команда GET PERFORMANCE. Интересно, как автор планировал что-либо стирать с ее помощью?
Ладно, оставим "дохлое" дело критики в стороне и продолжим нашу "шпионскую деятельность", наблюдая за процессом "прожига" лазерного диска. Последовательность, SCSI-команд, посылаемых устройству, будет следующей:
Листинг 2.5.333. Содержимое перехваченных CDB-блоков, посылаемых программой Alcohol 120% Алкоголиком устройству при "прожиге" образа лазерного диска
выбираем "прожиг" в меню
BB 00 FF FF FF FF ß SET SPEED
5A 00 2A 00 00 00 ß MODE SENSE
AC 00 00 00 00 52 ß GET PERFORMANCE
появляется диалог "запись"
1E 00 00 00 00 01 ß PREVENT REMOVAL (LOCK)
51 00 00 00 00 00 ß READ DISK INFORMATION
1E 00 00 00 00 00 ß PREVENT REMOVAL (UNLOCK)
запись диска в прогрессе
43 02 04 00 00 00 ß READ ATIP
51 00 00 00 00 00 ß READ DISK INFORMATION
…
52 00 00 00 00 00 ß READ TRACK/ZONE INFORMATION
5A 00 05 00 00 00 ß MODE SENSE
55 10 00 00 00 00 ß MODE SELECT
51 00 00 00 00 00 ß READ DISK INFORMATION
2A 00 FF FF D2 AC ß WRITE(10) -+
2A 00 00 00 D2 BC ß -----------+-- write Lead-In
2A 00 00 00 D2 CC ß -----------+
…
2A 00 00 00 65 B3 ß WRITE(10) -+
2A 00 00 00 65 CD ß -----------+-- write track
2A 00 00 00 65 E7 ß -----------+
В заключение отметим перечень SCSI-команд, непосредственно относящихся к записи и рекомендованных для внимательнейшего изучения. Это: BLANK, CLOSE TRACK/SESSION, FORMAT UNIT, READ BUFFER CAPACITY, READ DISC INFORMATION, READ MASTER CUE, READ TRACK INFORMATION, REPAIR TRACK, RESERVE TRACK, SEND CUE SHEET, SEND OPC INFORMATION, SYNCHRONIZE CACHE, WRITE (10). Все вышеперечисленные команды относятся к Стандарту MMC-1 и потому максимально просты для понимания. Сам же текст стандарта можно найти по адресуздесь: http://www.t10.org/ftp/t10/drafts/mmc/mmc-r10a.pdf.
Передача управления на функцию обработки сообщений
Двум предыдущим способам "реанимации" приложений присущи серьезные ограничения и недостатки. При тяжелых разрушениях стека, вызванных атаками типа buffer overfull или же просто алгоритмическими ошибками, содержимое важнейших регистров процессора окажется искажено, и мы уже не сможем ни совершить откат (стек утерян), ни выйти из текущей функции (EIP "смотрит" в "космос"). В консольных приложениях в такой ситуации действительно очень мало, что можно сделать… Вот GUI –— другое дело! Концепция событийно ориентированной архитектуры наделяет всякое оконное приложение определенными серверными функциями. Даже если текущий контекст выполнения необратимо утерян, мы можем передать управление на цикл извлечения и диспетчеризации сообщений, заставляя программу продолжить обработку действий пользователя.
Классический цикл обработки сообщений выглядит так как это показано в листинге 3.14.:
Листинг 3.14. Классический цикл обработки сообщений
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
Листинг 14 классический цикл обработки сообщений
Все, что нам нужно –— это передать управление на цикл while, даже не заботясь о настойке кадра стека, поскольку оптимизированные программы (а таковых большинство) адресуют свои локальные переменные не через EBP, а непосредственно через сам ESP. Конечно, при обращении к переменной msg, функция "угробит" содержимое стека, лежащее ниже его вершины, но это уже не важно.
Правда, при выходе из приложения оно "упадет" окончательно (ведь вместо адреса возврата из функции обработки сообщений, машинная команда RET обнаружит на вершине стека неизвестно что), но это произойдет после сохранения всех данных и потому никакой угрозы не несет. Исключение составляют приложения, "забывающие" закрыть все открытые файлы и перекладывающие эту работу на плечи функции ExitProcess.
Что ж! Можно так подправить адрес возврата, чтобы он указывал на функцию ExitProcess!
Давайте создадим простейшее Windows-приложение и поэкспериментирует с ним. Запустив
Microsoft Visual Studio выберем "New à Project à Win32 Application" и там –— "Typical Hello, World application". Добавим новый пункт меню, а в нем: char *p; *p = 0; и откомпилируем этот проект с отладочной информацией.
"Роняем приложение на пол" и, запустив отладчик, подгоняем мышь к первой строке цикла обработки сообщений и в появившемся контекстном меню находим пункт "Set Next Statement". Нажимаем клавишу <F5> для возобновления работы программы и… она действительно возобновляет свою работу!
А теперь откомпилируем наш проект в чистовом варианте (т. е. без отладочной информации) и попробуем реанимировать приложение в "голом" машинном коде. Пользуясь тем обстоятельством, что Windows –— это действительно многозадачная среда, в которой крушение одного процесса не мешает работе всех остальных, запустим свой любимый дизассемблер (например, IDA Pro) и проанализируем таблицу импорта отлаживаемой программы (вообще-то это может сделать и бесплатно распространяемый утилитой dumpbin, но его отчет не так нагляден).
Целью нашего поиска будут функции TranslateMessage/DispatchMessage и перекрестные ссылки, ведущие к циклу выборки сообщений (листинг 3.15).
Листинг 3.15. Поиск функций TranslateMessage/DispatchMessage в таблице импорта
.idata:004040E0 ; BOOL __stdcall TranslateMessage(const MSG *lpMsg)
.idata:004040E0 extrn TranslateMessage:dword ; DATA XREF: _WinMain@16+71^r
.idata:004040E0 ; _WinMain@16+8D^r
.idata:004040E4 ; LONG __stdcall DispatchMessageA(const MSG *lpMsg)
.idata:004040E4 extrn DispatchMessageA:dword ; DATA XREF: _WinMain@16+94^r
.idata:004040E8
Листинг 15 поиск функций TranslateMessage/DispatchMessage в таблице импорта
С функцией DispatchMessage связана всего лишь одна перекрестная ссылка, со всей очевидностью ведущая к искомому циклу обработки сообщений, дизассемблерный код которого выглядит так как показано в листинге 3.16.:
Листинг 3.16. Дизассемблерный листинг функции обработки сообщений
.text:00401050 mov edi, ds:GetMessageA
.text:00401050 ; первый вызов GetMessageA (это еще не цикл, это только его преддверье)
.text:00401050
.text:00401056 push 0 ; wMsgFilterMax
.text:00401058 push 0 ; wMsgFilterMin
.text:0040105A lea ecx, [esp+2Ch+Msg]
.text:0040105A ; ECX указывает на область памяти, через которую GetMessageA
.text:0040105A ; станет возвращать сообщение. текущее значение ESP может быть
.text:0040105A ; любым, главное, чтобы оно указывало на действительную область
.text:0040105A ; памяти (см. карту памяти, если значение ESP оказалось искажено
.text:0040105A ; настолько, что вывело его в "космос")
.text:0040105A ;
.text:0040105E push 0 ; hWnd
.text:00401060 push ecx ; lpMsg
.text:00401061 mov esi, eax
.text:00401063 call edi ; GetMessageA
.text:00401063 ; вызываем GetMessageA
.text:00401063
.text:00401065 test eax, eax
.text:00401067 jz short loc_4010AD
.text:00401067 ; проверка на наличие необработанных сообщений в очереди
.text:00401067
…
.text:00401077 loc_401077: ; CODE XREF: _WinMain@16+A9vj
.text:00401077 ; начало цикла обработки сообщений
.text:00401077
.text:00401077 mov eax, [esp+2Ch+Msg.hwnd]
.text:0040107B lea edx, [esp+2Ch+Msg]
.text:0040107B ; EDX указывает на область памяти, используемую для передачи сообщений
.text:0040107B
.text:0040107F push edx ; lpMsg
.text:00401080 push esi ; hAccTable
.text:00401081 push eax ; hWnd
.text:00401082 call ebx ; TranslateAcceleratorA
.text:00401082 ; вызываем функцию TranslateAcceleratorA
.text:00401082
.text:00401084 test eax, eax
.text:00401086 jnz short loc_40109A
.text:00401086 ; проверка на наличие в очереди необработанных сообщений
.text:00401086
.text:00401088 lea ecx, [esp+2Ch+Msg]
.text:0040108C push ecx ; lpMsg
.text:0040108D call ebp ; TranslateMessage
.text:0040108D ; вызываем функцию TranslateMessage, если есть что транслировать
.text:0040108D
.text:0040108F lea edx, [esp+2Ch+Msg]
.text:00401093 push edx ; lpMsg
.text:00401094 call ds:DispatchMessageA
.text:00401094 ; диспетчеризуем сообщение
.text:0040109A
.text:0040109A loc_40109A: ; CODE XREF: _WinMain@16+86^j
.text:0040109A push 0 ; wMsgFilterMax
.text:0040109C push 0 ; wMsgFilterMin
.text:0040109E lea eax, [esp+34h+Msg]
.text:004010A2 push 0 ; hWnd
.text:004010A4 push eax ; lpMsg
.text:004010A5 call edi ; GetMessageA
.text:004010A5 ; читаем очередное сообщений из очереди
.text:004010A5
.text:004010A7 test eax, eax
.text:004010A9 jnz short loc_401077
.text:004010A9 ; вращаем цикл обработки сообщений
.text:004010A9
.text:004010AB pop ebp
.text:004010AC pop ebx
.text:004010AD
.text:004010AD loc_4010AD: ; CODE XREF: _WinMain@16+67^j
.text:004010AD mov eax, [esp+24h+Msg.wParam]
.text:004010B1 pop edi
.text:004010B2 pop esi
.text:004010B3 add esp, 1Ch
.text:004010B6 retn 10h
.text:004010B6 _WinMain@16 endp
Листинг 16 дизассемблерный листинг функции обработки сообщений
Мы видим, что цикл обработки сообщений начинается с адреса 401050h и именно на этот адрес следует передать управление, чтобы возобновить работу "упавшей" программы. Пробуем сделать это и… программа работает!
Разумеется, настоящее приложение оживить намного сложнее, поскольку цикл обработки сообщений в нем рассредоточен по большому количеству функций, отождествить которые при беглом дизассемблировании невозможно. Тем не менее, приложения, построенные на основе общедоступных библиотек, (например, MFC (Microsoft Foundation Classes), OWVL (Object Windows Library),) обладают вполне предсказуемой архитектурой и реанимировать их вполне возможно.
Рассмотрим, как устроен цикл обработки сообщений в MFC. Большую часть своего времени исполнения MFC-приложения проводят внутри функции CWinThread::Run(void), которая периодически опрашивает очередь на предмет поступления свежих сообщений и рассылает их соответствующим обработчикам. Если один из обработчиков "споткнулся" и довел систему до критической ошибки, выполнение программы может быть продолжено в функции Run. В этом-то и заключается ее главная прелесть!
Функция не имеет явных аргументов, но принимает скрытый аргумент this, указывающей на экземпляр класса CWinThread или производный от него класс, без которого функция просто не сможет работать.
К счастью, таблицы виртуальных методов класса CWinThread содержат достаточно количество "родимых пятен", чтобы указатель this можно было воссоздать вручную.
Загрузим функцию Run в дизассемблер и отметим все обращения к таблице виртуальных методов, адресуемой посредствамчерез регистра ECX (листинг 3.17).
Листинг 3.17. Дизассемблерный листинг функции Run (фрагмент)
.text:6C29919D n2k_Trasnlate_main: ; CODE XREF: MFC42_5715+1F^j
.text:6C29919D ; MFC42_5715+67vj ...
.text:6C29919D mov eax, [esi]
.text:6C29919F mov ecx, esi
.text:6C2991A1 call dword ptr [eax+64h] ; CWinThread::PumpMessage(void)
.text:6C2991A4 test eax, eax
.text:6C2991A6 jz short loc_6C2991DA
.text:6C2991A8 mov eax, [esi]
.text:6C2991AA lea ebp, [esi+34h]
.text:6C2991AD push ebp
.text:6C2991AE mov ecx, esi
.text:6C2991B0 call dword ptr [eax+6Ch] ; CWinThread::IsIdleMessage(MSG*)
.text:6C2991B3 test eax, eax
.text:6C2991B5 jz short loc_6C2991BE
.text:6C2991B7 push 1
.text:6C2991B9 mov [esp+14h], ebx
.text:6C2991BD pop edi
.text:6C2991BE
.text:6C2991BE loc_6C2991BE: ; CODE XREF: MFC42_5715+51^j
.text:6C2991BE push ebx ; wRemoveMsg
.text:6C2991BF push ebx ; wMsgFilterMax
.text:6C2991C0 push ebx ; wMsgFilterMin
.text:6C2991C1 push ebx ; hWnd
.text:6C2991C2 push ebp ; lpMsg
.text:6C2991C3 call ds:PeekMessageA
.text:6C2991C9 test eax, eax
.text:6C2991CB jnz short n2k_Trasnlate_main
.text:6C2991CD
Листинг 17 дизассемблерный листинг функции Run (фрагмент)
Таким образом, функция Run ожидает получить указатель на двойное слово, указывающее на таблицу виртуальных методов, 0x19 и 0x1B элементы которой представляют собой функции PumpMessage и IsIdleMessage соответственно (или переходники к ним). Адреса импортируемых функций, если только динамическая библиотека не была перемещена, можно узнать в том же дизассемблере; в противном случае, следует отталкиваться от базового адреса модуля, отображаемого отладчиком по команде "Modules". При условии, что эти две функции не были перекрыты программистом, поиск нужной нам виртуальной таблицы не составит никакого труда.
По непонятным причинам библиотека MFC42.DLL не экспортирует символьных имен функций и эту информацию нам приходится добывать самостоятельно. Обработав библиотеку MFC42.LIB утилитой dumpbin, запущенной с ключом "/ARCH", мы определим ординалы [Y92] [n2k93] обеих функций (ординал PumpMessage –— 5307, а IsIdleMessage –— 4079). Остается найти эти значения в экспорте библиотеки MFC42.DLL (dumpbin /EXPORTS mfc42.dll > mfc42.txt), из чего мы узнаем что адрес функции PumpMessage: 6C291194h, а IsIdleMessage –— 6С292583h.
Теперь мы должны найти указатели на функции PumpMessage/IsIdleMessage в памяти, а точнее –— в секции данных, базовый адрес которой содержится в заголовке PE-файла, только помните, что в процессорах x86 наименее значимый байт располагается по меньшему адресу, т. е. все числа записываются в обратном порядке. К сожалению, отладчик Microsoft Visual Studio Debugger не поддерживает операцию поиска в памяти, и нам приходится действовать обходным путем –— копировать содержимое дампа в буфер обмена, вставлять его в текстовой файл и, нажав клавишу <F7> искать адреса уже там.
Долго ли, коротко ли, но интересующие нас указатели обнаруживаются по адресам 403044h/40304Сh (естественно, у вас эти адреса могут быть и другими).
Причем обратите внимание: расстояние между указателями в точности равно расстоянию между указателями на [EAX + 64h] и [EAX + 6Ch], а очередность их размещения в памяти обратна порядку объявления виртуальных методов. Это –— хороший признак и мы, скорее всего, находимся на правильном пути (листинг 3.18).
Листинг 3.18. Адреса функций IsIdleMessage/PumpMessage, найденные в секции данных
00403044 6C2911D4 6C292583 6C291194 ; IsIdleMessage/PumpMessage
00403050 6C2913D0 6C299144 6C297129
0040305C 6C297129 6C297129 6C291A47
Листинг 18 адреса функций IsIdleMessage/PumpMessage, найденные в секции данных
Указатели, указывающие на адреса 403048h/40304Ch, очевидно, и будут кандидатами в члены искомой таблицы виртуальных методов класса CWinThread. Расширив сферу поиска всем адресным пространством отлаживаемого процесса, мы обнаруживаем два следующих переходника (листинг 3.19).
Листинг 3.19. Переходники к функциям IsIdleMessage/PumpMessage, найденные там же
00401A20 jmp dword ptr ds:[403044h] ; IsIdleMessage
00401A26 jmp dword ptr ds:[403048h] ;
00401A2C jmp dword ptr ds:[40304Ch] ; PumpMessage
Листинг 19 переходники к функциям IsIdleMessage/PumpMessage, найденные там же
Ага, уже теплее! Мы нашли не сами виртуальные функции, но переходники к ним. Раскручивая этот запутанный клубок, попробуем отыскать ссылки на 401A26h/401A2Ch, которые передают управление на приведенный ранее код (листинг 3.20).
Листинг 3.20. Виртуальная таблица класса CWinThread
00403490 00401A9E 00401040 004015F0 ß 0x0, 0x1, 0x2 элементы
0040349C 00401390 004015F0 00401A98 ß 0x3, 0x4, 0x5 элементы
004034A8 00401A92 00401A8C 00401A86 ß 0x6, 0x7, 0x8 элементы
004034B4 00401A80 00401A7A 00401A74 ß 0x9, 0xA, 0xB элементы
004034C0 00401010 00401A6E 00401A68 ß 0xC, 0xD, 0xE элементы
004034CC 00401A62 00401A5C 00401A56 ß 0xF, 0x10, 0x11 элементы
004034D8 00401A50 00401A4A 00401A44 ß 0x12, 0x13, 0x14 элементы
004034E4 00401A3E 004010B0 00401A38 ß 0x15, 0x16, 0x17 элементы
004034F0 00401A32 00401A2C 00401A26 ß 0x18, 0x19, 0x1A элементы (PumpMessage)
004034FC 00401A20 00401A1A 00401A14 ß 0x1B, 0x1C, 0x1D элементы (IsIdleMessage)
Листинг 20 виртуальная таблица класса CWinThread
Даже неопытный исследователь программ распознает в этой структуре данных таблицу виртуальных функций. Указатели на переходники к функциям PumpMessage/IsIdleMessage разделяются ровно одним элементом, как того и требуют условия задачи. Предположим, что эта виртуальная таблица, которая нам и нужна. Для проверки этого предположения отсчитаем 0x19 (25) элементов верх от 4034F4h и попытаемся найти указатель, ссылающийся на ее начало. Если повезет и он окажется экземпляром класса CwinThread, тогда программа сможет корректно продолжить свою работу (листинг 3.21).
Листинг 3.21. Экземпляр класса CWinThread, вручную найденный нами в памяти
004050B8 00403490 00000001 00000000
004050C4 00000000 00000000 00000001
Листинг 21 экземпляр класса CWinThread, вручную найденный нами в памяти
Действительно, в памяти обнаруживается нечто похожее. Записываем в регистр ECX значение 4050B8h, находим в памяти функцию Run (как уже говорилось, если только она не была перекрыта, ее адрес –— 6C299164h –— известен). Нажимаем комбинацию клавиш <Ctrl>+<G>, затем вводим "0x6C299164" и в контекстном меню, вызванном правой клавишей мыши, выбираем Set Next Statement. Программа, отделавшись легким "испугом", продолжает свое исполнение, ну а мы на радостях идем пить пиво (кофе, квас, чай –— по вкусу).
Аналогичным путем можно вернуть к жизни и зависшие приложения, потерявшие нить управления и не реагирующие ни на мышь, ни на клавиатуру.
Подключение библиотеки ElByECC.DLL к своей программе
Существует по меньшей мере два способа подключения динамических библиотек к вашим программам. При динамической компоновке, адреса требуемых функций определяются посредством вызова GetProcAddress[Y77][n2k78] , причем сама библиотека ElByECC.DLL должна быть предварительно загружена с помощьючерез LoadLibray[Y79] [n2k80] . Это может выглядеть например так как показано в листинге 2.21 (обработка ошибок для просты опущена).:
Листинг 21.21. Динамическая загрузка библиотеки ElByECC.DLL
HANDLE h;
int (__cdecl *CheckECCAndEDC_Mode1) (char *userdata, char *header, char *sector);
h=LoadLibrary("ElbyECC.dll");
CheckECCAndEDC_Mode1 = GetProcAddress(h, "CheckECCAndEDC_Mode1");
Статическая компоновка предполагает наличие специального lib-файла, который может быть автоматически сгенерирован утилитой impliblib из пакета Borland C++ любой подходящей версии, представляющейую собой утилиту командной строки, вызываемую так: "implib.exe ?a ElByECC.lib ElByECC.lib".
Подключение дампа памяти
Для подключения дампа памяти к отладчику Windows Debugger (windbg.exe) в меню File
выберете пункт Crash Dump или воспользуйтесь "горячей" комбинацией клавиш <Ctrl>+<D>. В отладчике i386kd.exe для той же цели служит ключ "–z" командной строки, за которым следует полный путь к файлу дампа, отделенный от ключа одним или несколькими пробелами, при этом переменная окружения _NT_SYMBOL_PATH должна быть определена и должна содержать полный путь к файлам символьных идентификаторов, в противном случае отладчик аварийно завершит свою работу. Как один из вариантов, можно указать в командной строке ключ "–y" и тогда экран консоли будет выглядеть так:
i386kd –z C:\WINNT\memory.dmp -y C:\WINNT\Symbols, причем отладчик следует вызывать из Checked Build Environment/Free Build Environment консоли, находящейся в папке Windows 2000 DDK, иначе у вас ничего не получится.
Хорошая идея –— ассоциировать DMP-файлы с отладчиком i386kd.exe, запуская их одним нажатием клавиши <Enter> из менеджера FAR. Впрочем, выбор средства анализа –— дело вкуса. Кому-то нравиться KAnalyze, а кому-то достаточно и простенького DumpChk. Выбор аналитических инструментов чрезвычайно велик (один лишь DDK содержит четыре из них!) и, чтобы хоть как-то определиться с выбором, мы остановимся на i386kd.exe, также называемом Kernel Debugger.
Как только консоль отладчика появится на экране (а Kernel Debugger –— это консольное приложение, горячо любимое всеми, кто провел свою молодость за текстовыми терминалами), курсор наскоро дизассемблирует текущую машинную инструкцию и своим тревожным мерцанием затягивает нас в пучину машинного кода. Ну что, будем глазки строить или все-таки дизассемблировать? –— незлобно ворчим мы, выбивая на клавиатуре команду "u", заставляющую отладчик продолжить дизассемблирование.
Судя по символьным идентификаторам PspUnhandledExceptionInSystemThread и KeBugCheckEx мы находимся глубоко в ядре, а точнее –— в окрестностях того кода, что выводит BSOD на экран (листинг 3.22).
Откроем его: Панель управления à Администрирование à Просмотр событий (листинг 3.24).
Листинг 3.24. Копия "голубого экрана смерти", сохраненная в системном журнале
Компьютер был перезагружен после критической ошибки:
0x0000001e (0xc0000005, 0xbe80b000, 0x00000000, 0x00000000).
Microsoft Windows 2000 [v15.2195]
Копия памяти сохранена: C:\WINNT\MEMORY.DMP.
Листинг 24 копия голубого экрана смерти, сохраненная в системном журнале
Отталкиваясь от категории критической ошибки (0x1E), мы без труда сможем определить адрес инструкции-убийцы –— 0xBE80B000 (в приведенном листинге 3.24 он выделен полужирным шрифтом). Применяем команду u BE80B000 для просмотра его содержимого и видим следующий результат (листинг 3.25).
Листинг 3.25. Результат дизассемблирования дампа памяти по адресу, сообщенному "голубым экраном смерти"
kd>u 0xBE80B000
be80b000 a100000000 mov eax,[00000000]
be80b005 c20800 ret 0x8
be80b008 90 nop
be80b009 90 nop
be80b00a 90 nop
be80b00b 90 nop
be80b00c 90 nop
be80b00d 90 nop
Листинг 25 результат дизассемблирования дампа памяти по адресу, сообщенному голубым экраном смерти
Ага! Вот это уже больше похоже на истину! Инструкция, на которую указывает курсор (в тексте она выделена черным цветом), обращается к ячейке с нулевым адресом, возбуждая тем самым губительное для системы исключение. Теперь мы точно знаем, какая ветка программы вызвала сбой.
Хорошо, а как быть, если копии экрана смерти в нашем распоряжении нет? На самом деле, синий экран всегда с нами, надо только знать где искать! Попробуйте открыть файл дампа в любом HEX-редакторе и вы обнаружите следующие строки (листинг 3.26).
Листинг 3.26. Копия голубого экрана в заголовке дампа программы
00000000: 50 41 47 45 44 55 4D 50 ¦ 0F 00 00 00 93 08 00 00 PAGEDUMP0 У•
00000010: 00 00 03 00 00 80 8B 81 ¦ C0 A4 46 80 80 A1 46 80 ¦ АЛБLдFААбFА
00000020: 4C 01 00 00 01 00 00 00 ¦ 1E 00 00 00 05 00 00 C0 LO O ^ ¦ L
00000030: 00 B0 80 BE 00 00 00 00 ¦ 00 00 00 00 00 41 47 45 -А- AGE
Листинг 26 копия голубого экрана в заголовке дампа программы
С первого же взгляда удается опознать все основные Bug Check-параметры: 1E 00 00 00 –— это код категории сбоя 0x1E (на процессорах x86 наименее значимый байт располагается по меньшему адресу, то есть все числа записываются в обратном порядке); 05 00 00 C0 –— код исключения ACCESS VIOLATION; а 00 B0 80 BE –— и есть адрес машинной команды, породившей это исключение. В комбинации же 0F 00 00 00 93 08 легко узнается номер билда системы, стоит только записать его в десятичной нотации.
Для просмотра Bug Check-параметров в более удобочитаемом виде можно воспользоваться следующей командой отладчика: dd KiBugCheckData (листинг 3.27).
Листинг 3.27. Bug Check-параметры, отображаемые в удобочитаемом виде
kd> dd KiBugCheckData
dd KiBugCheckData
8047e6c0 0000001e c0000005 be80b000 00000000
8047e6d0 00000000 00000000 00000001 00000000
8047e6e0 00000000 00000000 00000000 00000000
8047e6f0 00000000 00000000 00000000 00000000
8047e700 00000000 00000000 00000000 00000000
8047e710 00000000 00000000 00000000 00000000
8047e720 00000000 00000000 00000000 00000000
8047e730 00000000 e0ffffff edffffff 00020000
Листинг 27 Bug Check параметры, отображаемые в удобочитаемом виде
Другие полезные команды: !drivers –— выводящая список драйверов, загруженных на момент сбоя, !arbiter –— показывающая всех арбитров вместе с диапазонами арбитража, !filecache –— отображающая информацию о кэше файловой системы и PT, !vm –— отчитывающаяся об использовании виртуальной памяти и т. д. и т. п. –— всех не перечислишь! (полный перечень команд вы найдете в руководстве по своему любимому отладчику).
Конечно, в реальной жизни определить истинного виновника краха системы намного сложнее, поскольку всякий нормальный драйвер состоит из множества сложно взаимодействующих функций, образующих запутанные иерархические комплексы, местами пересеченные туннелями глобальных переменных, превращающих драйвер в самый настоящий лабиринт. Приведем только один пример. Конструкция вида mov eax, [ebx], где ebx == 0, работает вполне нормально, послушно возбуждая исключение, и пытаться "поговорить с ней по-мужски" –— бессмысленно! Нужно найти тот код, который записывает в регистр EBX нулевое значение, и сделать это непросто. Можно, конечно, просто прокрутить экран вверх, надеясь, что на данном участке программный код выполнялся линейно, но никаких гарантий, что это действительно так у нас нет, равно как нет и возможности обратной трассировки (back trace). Грубо говоря, адрес предшествующей машинной инструкции нам неизвестен и "закладываться" на прокрутку экрана нельзя!
Загрузив подопытный драйвер в любой интеллектуальный дизассемблер, автоматически восстанавливающий перекрестные ссылки (например, дизассемблер IDA Pro), мы получим более или менее полное представление о топологии управляющих ветвей программы. Конечно, дизассемблирование в силу своей статической природы, не гарантирует, что управление не перекинулось откуда-то еще, но, по крайней мере, сужает круг поиска. Вообще же, о дизассемблировании написано множество хороших книг (и "Фундаментальные основы хакерства" Криса Касперски в том числе[Y96] [n2k97] ), поэтому не будем останавливаться на этом вопросе, а просто пожелаем всем читателям удачи[Y98] [n2k99] .
Рис.унок 3.5. Отладчик i386kd за работой (пиво, бутерброды прилагаются); несмотря на свою отталкивающую внешность это чрезвычайно мощный и удобный в работе инструмент, позволяющий проворачивать умопомрачительные пассажи нажатием всего пары-тройки клавиш (одна из которых вызывает ваш собственный скрипт).
Рис.унок 3.6. Windows Dedbugger c загруженным дампом памяти. Обратите внимание: отладчик самостоятельно высвечивает Bug Check-коды, не ожидая пока мы об этом его попросим, а при попытке дизассемблирования инструкции, возбудившей исключение, на экране выскакивает "Module Load: W2K_KILL.SYS", сообщающая нам имя драйвера-убийцы. Вроде бы мелочь, а как приятно!
методы низкоуровневого управления приводами 1
интерфейсы взаимодействия с оборудованием 32
доступ через CD-ROM-драйвер 44
доступ через cooked-моде (режим блочного чтения) 12611
доступ через SPTI 15614
доступ через ASPI 32631
доступ через SCSI-порт 43641
доступ через SCSI-мини порт 46646
взаимодействие через порты ввода/вывода 56656
доступ через MSCDEX драйвер 68667
взаимодействие через собственный драйвер 71670
сводная таблица характеристик различных интерфейсов 71671
способы разоблачения защитных механизмов 72672
>>>>> отжиг дисков. за, против и немного вокруг 75674
>>>>> блокирование/разблокирование кнопки EJECT 79678
>>>>> хакерские секреты Рецепты тормозной жидкости для CD 81680
примеры исследования реальных программ 82681
Alcohol 120% 82681
Easy CD Creator 83682
Clone CD 83682
Полином локатора ошибки
Полученный синдром описывает конфигурацию ошибки, но еще не говорит нам, какие именно символы полученного сообщения были искажены. Действительно, степень синдромного полинома, равная 2t, много меньше степени полинома сообщения, равной n, и межу их коэффициентами нет прямого соответствия. Полином, коэффициенты которого напрямую соответствуют коэффициентам искаженных символов, называется полиномом локатора ошибки и по общепринятому соглашению обозначается греческой буквой L (ламбда).
Если количество искаженных символов не превышает t, между синдромом и локатором ошибки существует следующее однозначное соответствие, выражаемое следующей формулой: НОД [xn – -1, E(x)] = L?(x) и вычисление локатора сводится к задаче нахождения наименьшего общего делителя, успешно решенной еще Евклидом и элементарно реализуемой как на программном, так и на аппаратном уровне. Правда, за простоту реализации нам приходиться расплачиваться производительностью, точнее непроизводительностью
данного алгоритма, и на практике обычно применяют более эффективный, но и более сложный для понимания алгоритм Берлекэмпа-Месси (Berlekamp-Massy), подробно описанный Кнутом во втором томе "Искусства программирования" (см. так же книгу "Теория и практика кодов, контролирующих ошибки" Блейхута) и сводящейся к задаче построения цепи регистров сдвига с линейной обратной связью и по сути своей являющегося разновидностью авторегрессионого фильтра, множители в векторах которого и задают полином L.
Декодер, построенный по такому алгоритму, требует не более 3t операций умножения в каждой из итерации, количество которых не превышает 2t. Таким образом, решение поставленной задачи укладывается всего в 6t2 операций умножения. Фактически, поиск локатора сводится к решению системы из 2t
уравнений — по одному уравнению на каждый символ синдрома, — c t неизвестными. Неизвестные члены и есть позиции искаженных символов в кодовом слове v. Легко увидетьвидеть, что если количество ошибок превышает t, то система уравнений становится неразрешима и восстановить разрушенную информацию в этом случае не представляется возможным.
Блок-схема алгоритма Берлекэмпа-Месси приведена на рис. 21.55, а его законченная программа реализация содержится в листинге 1.2.
Рис. 21.55. . 0x339 Структурная схема алгоритм Берлекэмпа-Месси
Полиномиальная арифметика
Полиномиальной арифметике посвящен шестой раздел третьего тома "Искусства программирования" Дональда Кнута, где полиному дается следующее определение: "Формально говоря, полином над S представляет собой выражение вида: u(x)= unxn + … + u1x + u0, где коэффициенты un,…, u1, u0 — элементы некоторой алгебраической системы S, а переменная x может рассматриваться как формальный символ без определяющего значения. Будем полагать, что алгебраическая система S представляет собой коммутативное кольцо с единицей. Это означает, что S допускает операции сложения, вычитания и умножения, удовлетворяющие обычным свойствам: сложение и умножение являются ассоциативными и коммутативными бинарными операциями, определенными на S, причем умножение дистрибуьютивно по отношению к сложению. Существует так же единичный элемент по сложению 0 и единичный элемент по умножению 1, такие, что a + 0 == a и a * 1 == a для всех a из S. Вычитание является обратной по отношению к сложению операцией, но о возможности деления как операции, обратной по отношению к умножению, ничего не предполагается. Полином 0xn + m + … + 0x n + 1 + unxn + … + u1x + u0
рассматривается как идентичный unxn + … + u1x + u0, хотя формально он отличается от него".
Таким образом, вместо того, чтобы представлять информационное слово D, кодовое слово C
и остаток от деления R в виде целых чисел (как это делалось нами ранее), мы можем связать их с соответствующими коэффициентами двоичного полинома, выполняя все последующие математические манипуляции по правилам полиномиальной арифметики. Выигрыш от такого преобразования на первый взгляд далеко не очевиден, но не будем спешить, а лучше преобразуем любое пришедшее нам в голову число (например, 69h) в двоичный полином. Запустив "Калькулятор" или любое другое подходящее приложение по вашему вкусу, переведем наше число в двоичный вид (при соответствующих навыках эту операцию можно выполнить и в уме, см. "Техника и философия хакерских атак" Криса Касперски[Y64] [n2k65] ): 69h à 1101001.
Ага, крайний правый коэффициент равен единице, затем следуют два нулевых коэффициента, потом единичный коэффициент… короче говоря, получается следующее: 1x6 + 1x5 + 0x4 + 1x3+ 0x2 + 0x + 1. По сути говоря, битовая строка "1101001" является одной из форм записи вышеуказанного полинома, — ненаглядной с точки зрения неподготовленного человека, но удобной для машинной обработки. Постойте, но если 69h уже
представляет собой полином, то в чем разница между сложением полиномов 69h и 27h и сложением целых чисел 69h и 27h?! Разница несомненно есть. Как еще показал Ницше: фактов нет, а есть одни лишь интерпретации. Интерпретация же чисел и полиномов различна и математические операции над ними выполняются по совершенно независимым правилам.
Коэффициенты в полиномиальной арифметики строго типизированы и коэффициент при xk имеет иной тип нежели при xm (конечно, при том условии, что k ¹ m). А операции над числами различных типов категорически не допустимы! Все коэффициенты обрабатываются независимо, а возникающий при этом перенос в старший разряд (заем из старшего разряда) попросту не учитывается. Покажем это на примере сложения чисел 69h и 27h (листинг 2.11). Сложение, выполненное по правилам полиномиальной двоичной арифметики (слева) и сложение, выполненное по правилам обычной арифметики (справа). :
Листинг 21.11. Пример сложения чисел 69h и 27hСложение, выполненное по правилам полиномиальной двоичной арифметики (слева) и сложение, выполненное по правилам обычной арифметики (справа)
1101001 (69h) 1101001 (69h)
+0100111 (27h) +0100111 (27h)
––––––– –––––––
1001110 (4Eh) 10010000 (90h)
Простейшие расчеты показывают, что сложение полиномов по модулю два, дает тот же самый результат, что их вычитание и "волшебным" образом совпадает с битовой операцией XOR (исключающее ИЛИ). Впрочем, совпадение с этой операциейXOR — чистая случайность, но вот эквивалентность сложения и вычитания заставляет заново пересматривать привычную природу вещей, вспоминая задачки из серии "у Маши было одно яблоко, Петя отнял у нее его, затем ей подарил еще одно, спрашивается: сколько всего яблок у Маши осталось? А сколько у нее было бы, если бы первое яблоко осталось не отнятым?".
С точки зрения арифметики по модулю два ответ: один и ноль соответственно. Да! Не отними бы Петя у Маши яблоко, 1 + 1 == 0 и бедная Маша вообще осталась бы ни с чем. Так что мальчики, почаще отнимайте яблоки и девушек — учите их компьютерной грамотности!
Впрочем, мы отвлеклись. Ладно, оставим в покое половые разборки между Петей и Машейчленами молодежи ив вернемся к фиктивному члену x
нашего полинома и его коэффициентам. Благодаря их типизации и отсутствию взаимных связей, мы можем осуществлять обработку сколь угодно длинных чисел, просто выполняя на потоке операцию XOR (исключающее ИЛИ)'я над составляющимие их битамиы на потоке. Это и есть одно из тех достоинств полиномиальной арифметики, которые не видны с первого взгляда, но благодаря которым полиномиальная арифметика стала так широко распространена.
Однако, в нашем случае одной лишь полиномиальной арифметикой дело не обходится и для реализации кодера/декодера Рида-Соломона нам потребуется активная помощь со стороны полей Галуа. Что же это за поля такие, спросите Вы?
Полиномиальная арифметика и поля Галуа
В прошломй разделе главе этойго главы раздела мы говорили о том, что помехоустойчивые коды Рида-Соломона основаны на двух фундаментальных математических составляющих: полиномиальный арифметике и арифметике полей Галуа. До тех пор, пока эти вопросы не будут нами всесторонне рассмотрены, мы не сможем двигаться дальше и потому наберемся чуточку терпения, чтобы совершить решительный штурм математических вершин. После чего начнется чистое программирование, практически без примесей всяких инородных математик.
Поля Галуа
В далеких шестидесятых, когда компьютеры были большими, а двадцати мегабайтовые винчестеры емкостью в 20 Мбайт напоминали собой стиральные машины, родилась одна из красивейших легенд о зеленом инопланетном существе, прилетевшим со звезд, и записавшим всю Британскую энциклопедию на тонкий металлический стержень нежно-серебристого цвета, который существо и увезло с собой. Сегодня, когда габариты 100 ГГб жестких дисков сократились до размеров сигаретной пачки, такая плотность записи информации уже не кажется удивительной и даже вызывает улыбку. Но! Все дело в том, что инопланетное существо обладало технологией записи бесконечного количества информации на бесконечно крошечном отрезке и Британская энциклопедия была выбрала лишь для примера. С тем же успехом инопланетянин мог скопировать содержимое всех серверов Интернета, нанеся на свой металлический стержень всего одну-единственную риску. Не верите? А зря! Переводим Британскую энциклопедию в цифровую форму, получая огромное преогромное число. Затем — ставим впереди него запятую, преобразуя записываемую информацию в длиннющую десятичную дробь. Теперь только остается найти два числа A и B, таких, что результат деления A и B
как раз и будет равен данному числу с точностью до последнего знака. Запись этих чисел на металлических стержень осуществляется нанесением риски, делящей последний на два отрезка с длинами, кратными величинам А и B
соответственно. Для считывания информации достаточно всего лишь измерить длины отрезков А и B, а затем — поделить один на другой. Первый десяток чисел после запятой будет более или менее точен, ну а потом… Потом жестокая практика "опустит" абстрактную теорию по самые помидоры, окончательно похоронив последнюю под толстым слоем информационного мусора, возникающего из невозможности точного определения геометрических размеров объектов реального мира.
В цифровом мире дела обстоят еще хуже. Каждый программист знает, что на деление целых и вещественных чисел наложены достаточно жесткие ограничения.
Помимо того, что деление весьма прожорливая в плане процессорных ресурсов операция, так она еще и математически неточная! То есть, если c = a * b, то еще не факт, что a == c/b! Таким образом, для практической реализации кодов Рида-Соломона обычная арифметика непригодна и приходится прибегать к помощи особой математики — математики конечных групп Галуа.
Под группой здесь понимается совокупность целых чисел, последовательно пронумерованных от 0 до 2n – 1, например: {0, 1, 2, 3} или {00h 01h, 02h, 03h, 04h, 05h, 06h, 07h, 08h, 09h, 0Ah, 0Bh, 0Ch, 0Dh, 0Eh, 0Fh}. Группы, содержащие 2n элементов, называются полями Галуа (Galois Field) и обозначаются так: GF(2n).
Замечание
На самом же деле, полями Галуа называют любые конечные поля, но в данном контексте мы будем говорить лишь о тех полях, количество членов которых равно 2n.
Члены групп в обязательном порядке подчиняются ассоциативному, коммутативному и дистрибуьютивному законам, но обрабатываются довольно противоестественным на первый взгляд образом:
1. Сумма двух любых членов группы всегда присутствует в данной группе.
2. Для каждого члена "а" группы существует тождественный (identity)
ему член, обычно записываемый как "e", удовлетворяющий следующему условию: a + e = e + a = a.
3. Для каждого члена "a" группы, существует обратный (inverse) ему член " –a", такой что: a + –a == 0.
Начнем с первого тезиса. Не кажется ли он вам бредом? Допустим, у нас есть группа {0, 1, 2, 3}. Это в какоим же состояниив дупель пьяным нужно быть, чтобы при вычислении значения 2 + 3 получить число меньшее или равное 3?! Оказывается, сложение в полях Галуа осуществляется без учета переноса и сумма двух членов группы равна: c = (a + b) % 2n, где операция ""%" обозначает взятие остатка.
Применительно к нашему случаю: (2 + 3) % 4 == 1. У математиков это называется "сложением по модулю 4".
Естественно, вас интересует: а применяется ли сложение по модулю на практике или используется лишь в абстрактных конструкциях теоретиков? Хороший вопрос! Сложение по модулю мы машинально выполняем десятки раз на дню, даже не задумываясь о том, что это и есть сложение без учета переноса. Вот, например, проснувшись в шесть вечера по утру, вы просидели за компьютером девять часов кряду, а потом неожиданно бросили взгляд на свои наручные часы. Какое положение занимала часовая стрелка в это время, при условии, что часы идут точно? Искомое значение со всей очевидностью представляет собой сумму 6 и 9 по модулю 12 и равно оно: (6 + 9) % 12 == 3. Вот вам наглядный пример практического использования арифметики Галуа. А теперь давайте в порядке эксперимента вычтем из числа 3 число 6… (если не догадалисываясь как это правильно сделать, то — возьмите в руки часы).
Теперь самое главное: раз, результат деления одного члена группы на другой, естественно, неравный нулю член, в обязательном порядке должен присутствовать в данной группе, то несмотря на то, что деление осуществляется в целых числах оно будет точным. Точным, а не округленным! Следовательно, если c = a * b, то a == c/b. Другими словами, умножение и деление непротиворечивым образом определено для всех членов группы, конечно, за исключением невозможности деления на нуль, причем, расширения разрядной сетки при умножении не происходит!
Конечно, это не совсем обычное умножение (и далеко не во всяком поле Галуа дважды два будет рано четырем), однако, никто и не требует от арифметики Галуа ее соответствия "здравому смыслу" и "житейскому опыту". Главное, — что она работает, причем работает хорошо. И существование жестких дисков, CD-ROM/DVD приводов — лучшее тому подтверждение, ибо все они так или иначе используют эту арифметику в своих целях.
Как уже говорилось, в вычислительной технике наибольшее распространение получили поля Галуа с основанием 2, что объясняется естественностью этих полей с точки зрения машинной обработки, двоичной по своей природе.
Для реализации кодера/декодера Рида-Соломона нам потребуются четыре базовых арифметических операции: сложение, вычитание, умножение и деление. ДалееНиже они будут рассмотрены во всех подробностях.
Полная нейтрализация защиты
Получить работоспособную копию защищенного диска –— это только полдела. Законченный взлом подразумевает как минимум восстановление искаженного TOC и отвязку защиты от диска. Другими словами, корректно взломанный диск должен копироваться на любом оборудовании, любыми штатными копировщиками и при этом не должен ни с чем конфликтовать.
Процесс отвязки обычно начинается с анализа геометрии диска на предмет выявления реально используемых сессий/секторов. Искаженные сессии обычно не несут никакой полезной нагрузки –— для защиты важен лишь сам факт их существования, но отнюдь не содержимое, которое на некоторых приводах вообще не доступно. А какие существуют способы проверки секций на существование? На "железном" уровне способ один –— компьютер посылает приводу SCSI/ATAPI команду READ TOC и получает в ответ полный каталог содержимого. На программном уровне взаимодействие с аппаратурой чаще всего осуществляется посредством ASPI/SPTI-интерфейса, реже –— непосредственно через порты ввода-вывода. Некоторые защиты предпочитают действовать через драйвер привода CD-ROM, полагая что этот способ более цивилизован и менее опасен.
В любом случае у хакера есть два пути –— либо локализовать команду чтения TOC в машинном коде (обычно это делается установкой точки останова на функцию SendASPI32Command) и попытаться переписать код программы так, чтобы она корректно работала с любым TOC, либо путем перехвата функции CreateFile/DeviceIoContorl внедрить "шпиона", отслеживающего весь проходящий через него поток SCSPI/ATAPI-команд и, в случае чтения TOC защищенного диска, возвращающего защите подложные данные. Первый путь боле надежен, а, значит, и более практичен. Рассмотрим его поподробнее.
Получение доступа к удаленным файлам
Прежде, чем приступить к восстановлению удаленных файлов, давайте-ка вспомним основные принципы организации файловой системы ISO9660 (Joliet). Итак, шестнадцатый сектор первого трека каждой сессии жестко закреплен за дескриптором тома. Его легко узнать по сигнатуре "CD001", хранящейся в секторе по смещению 1. Если это действительно так (ну мало ли, вдруг нам подсунули диск без файловой системы, например, аудиодиск), тогда по смещению 156 в секторе корневого каталога [n2k18] расположена записьструктура Directory Record корневой директории. Наибольший интерес представляют ее следующие поля: длина самой Directory Record (байт по смещению 0), стартовый LBA-адрес файла/вложенной директории (двойное слово в low-endian формате по смещению 2), длина файла/вложенной директории (двойное слово в low-endian формате по смещению 10), атрибуты файла (байт по смещению 25), длина имени файла/вложенной директории (байт по смещению 32) и, наконец, непосредственно само имя файла (цепочка байт, начинающаяся со смещения 33). Файловая система Joliet устроена аналогичным образом, но соответствующий ей дескриптор тома расположен не в шестнадцатом, а в семнадцатом секторе.
Если первый, считая от нуля, бит атрибутов равен единице, то мы имеет дело с вложеннымой каталогомдиректорией[n2k19] , в противном случае –— с файлом. Вложенные каталогидиректории представляют собой совокупность записей типа Directory Record, каждая из которых указывает либо на целевой файл либо на очередную вложенную директорию.
Таким образом, для просмотра содержимого произвольной сессии нам потребуется всего лишь узнать стартовый адрес ее первого трека. Эту информацию легко получить путем чтения TOC'а на "сыром" уровне (команда: 43h, формат: 02h). Так же, можно воспользоваться любой утилитой "прожига" дисков, способной выдавать информацию о геометрии диска (для этой цели подходяит, в частности,, Roxio Easy CD Creator, Stomp Record Now, Nero – Burning Rom и многие другие).
Увеличив стартовый адрес первого трека на 16 секторов (для работы с ISO- 9660) или на 17 секторов (для работы с Joliet), мы попадем на описатель тома, то есть –— нана корневойую каталогдиректорию. А дальше уже рекурсивный спуск по древу каталоговдиректорий пойдет "как по маслу" (под Windows можно даже не заботиться об исчерпании стека, если, конечноа, файловая система не содержит грубых ошибок, приводящих к ее зацикливанию. Между прочим, такие трюки с зацикливанием достаточно часто используются в защитах).
Остается лишь пролистать каталогидиректории всех дисковых сессий на предмет поиска файлов, отсутствующих в каталогедиректории самой последней сессии. При этом следует обращать внимание не только на имена файлов, но и на их стартовые адреса. Файлы с идентичными именами, но различными стартовыми адресами –— это различные файлы! Например, если вы периодически сохраняете свой текущий проект на лазерный диск, все время записывая его под одним и тем же именем, то все предыдущие версии этого файла для штатных средств операционной системы оказываются утерянными. Однако, "ручной" просмотр содержимого сессий позволяет моментально восстановить любую из ранее записанных версий файла! Между прочим, необходимость подобного восстановления на практике возникает очень и очень часто, поэтому такое умение лишним не будет!
Для последующих экспериментов нам потребуется утилита ISO9660.dir, которая расположена на прилагающимся к книге компакт-диске. Используя любую программу "прожига" запишем на диск CD-RW/CD-R диск три сессии с таким расчетом, чтобы в первой или второй сессии образовалось несколько удаленных файлов, которые нам, собственно, и предстоит найти.
Допустим, стартовые адреса дисковых сессий располагались так как это (показано всм. листинге 10.1. Трек номер AA представляет собой выводную область и не представляет для нас никакого интереса., приведенный ниже):
Листинг 10.1. Стартовые адреса первых треков каждой из трех сессий диска
> ISO9660.dir.exe 1.1
track | Start LBA
------+-----------
1 | 0
2 | 13335
3 | 22162
AA | 24039
Листинг 1 стартовые адреса первых треков каждой из трех сессий диска (трек номер AA представляет собой выводную область и не представляет для нас никакого интереса)
Теперь, последовательно вызывая утилиту ISO9660.dir.exe со стартовыми адресами 0, 13 335 и 22 162, мы увидим содержимое каталогов директорий первой, второй и третьей сессий соответственно. Для удобства сравнения результат работы программы представлен в виде горизонтальной таблицы (листинг 10.2). При сравнении видно, что вторая сессия содержит удаленный файл See You.mp3 (в тексте он выделен полужирным шрифтом), отсутствующий в оглавлении третьей сессии.:
Листинг 10.2. Сравнение содержимого каталогов трех сессий
>ISO9660.dir 1.1 0 -–Joliet |
>ISO9660.dir 1.1 13335 –Joliet |
>ISO9660.dir 1.1 22162 -Joliet |
start|size |name -----+-------+----------------- 22 |2048 |. 22 |2048 |.. 25 |3591523|PersonalJesus.mp3 |
start|size |name -----+-------+----------------- 13357|2048 |. 13357|2048 |.. 25 |3591523|PersonalJesus.mp3 13360|3574805|See You.mp3 |
start|size |name -----+-------+----------------- 22184|2048 |. 22184|2048 |.. 25 |3591523|PersonalJesus.mp3 22187|3472405|Strangerlove.mp3 |
Видите, вторая сессия содержит файл "See You.mp3", который отсутствует в оглавлении третьей сессии, а это, значит, что штатным средствам операционной системы он категорически недоступен! И действительно, команда dir показывает всего лишь два файла (листинг 10.3). Но нас-то не проведешь! Мы-то уже знаем, что здесь содержится один удаленный файл.:
Листинг 10.3. Таким представляется содержимое диска операционной системе
Том в устройстве G имеет метку NEW
Серийный номер тома: 4171-70DC
Содержимое папки G:\
30.06.2003 00:10 3 591 523 01 - Personal Jesus.mp3
30.06.2003 00:12 3 472 405 03 - Strangerlove.mp3
2 файлов 7 063 928 байт
0 папок 0 байт свободно
Листинг 3 таким представляется содержимое диска операционной системе. Но нас-то не проведешь! Мы-то уже знаем, что здесь содержится один удаленный файл
Для восстановления файла достаточно дать запустить нашу утилиту со следующими ключами: ISO9660.dir.exe 1.1 "See You.mp3" 13360 3574805 или воспользоваться любой программой для "грабежа" секторов с диска (но тогда нам придется вручную усечь "хвост" последнего сектора на необходимую величину). Короче говоря, мы должны считать 3 .574 .805 байт, начиная с сектора 13. 360 и до последнего сектора файла включительно (в силу отсутствия фрагментации сектора, принадлежащие файлу, всегда располагаются последовательно).
Если все сделано правильно, то на жестком диске образуется файл See You.mp3, который теперь можно проиграть любым MP3-плеером, наслаждаясь своей любимой музыкой. А можно, – используя любую программу "прожига", "закинуть" восстановленный файл на тот же самый лазерный диск, дописав к нему еще одну сессию. Конечно, это далеко не самое лучшее и элегантное решение (ведь теперь восстановленный файл будет записан дважды), но, к сожалению, ни одна из известных мне утилит "прожига" не позволяет вмешиваться в процесс генерации файловой системы, и не позволяет вручную формировать ссылки на файлы предшествующих сессий.
Теперь подготовим следующий диск для восстановления. Запишем на болванку какой-нибудь файл. Затем, слегка изменив его содержимое, запишем под тем же самым именем на диск еще один или несколько раз.
Убедимся, что при просмотре оглавления штатными средствами Windows мы видим всего лишь один файл –— тот, что был записан последним. А теперь вообразим себе, что нам жизненно потребовалось получить доступ к одной из его предшествующих версий. No problem как говорят англичане! Запускаем утилиту ISO9660.dir и видим, что на диске имеются две следующих сессии (листинг 10.4).:
Листинг 10.4. На восстанавливаемом диске имеются две сессии со стартовыми адресами 0 и 12000 соответственно
>ISO9660.dir.exe 1.1
track | Start LBA
------+-----------
1 | 0
2 | 12000
AA | 12600
Листинг 4 На восстанавливаемом диске имеются две сессии со стартовыми адресами 0 и 12.000 соответственно
Последовательно запуская утилиту ISO9660.dir с ключами 0 и 12 .000 мы обнажим, что… А, впрочем, давайте не будем забегать вперед. Сейчас вы сможете увидеть все это сами (листинг 10.5).:
Листинг 10.5. Обе сессии содержат один и тоже файл asm.drf.zip, однако, стартовые адреса файла в обоих случаях не совпадают, да и длина различна тоже
> ISO9660.dir.exe 1.1 0 –Joliet |
> ISO9660.dir.exe 1.1 12000 -Joliet |
start | size | name ----------+----------+------------ 22 |2048 |. 22 |2048 |.. 25 |38189 |asm.drf.zip |
start | size | name ----------+----------+--------------- 12022 |2048 |. 12022 |2048 |.. 12025 |354533 |asm.drf.zip |
На первый взгляд здесь все нормальнопучком:, файл asm.drf.zip присутствует в обоих сессиях, и невнимательному наблюдателю может показаться, что в обоих случаях речь идет про один и тот же файл. Однако, более подробный анализ показывает, что стартовые адреса файла не совпадают, следовательно, мы имеем дело с двумя различными файлами! Длины файлов также различны и, судя по всему, последний файл является более свежей версией своего собрата.
Добыть предшествующую версию файла можно так: ISO9660.dir.exe 1.1 asm.drf.zip 25 38189. И этот прием действительно работает!
Разумеется, для практического использования утилита ISO9660.dir.exe катастрофически неудобна. Однако, не судите ее строго, ведь это всего лишь демонстрационный пример. Надеясь пробудить в своих читателях стремление к творчеству я предлагаю всю недостающую функциональность реализовать самостоятельно. Задача минимум –— "обернуть" утилиту в комфортный графический (а еще лучше консольный) интерфейс, например, написать "плагин" (Plug-In)плагин к файловому менеджеру FAR'у, отображающий содержимое выбранной сессии на панели. Это гораздо проще, чем запрограммировать полноценный дисковый драйвер, а эффект с точки зрения пользователя все равно один и тот же. Задачу сравнения имен и стартовый адресов файлов так же не помешало бы возложить на компьютер, поскольку ручной поиск удаленных (замещенных) файлов крайне непродуктивен –— типичный CD-ROM содержит тысячи, если не десятки тысяч файлов, рассредоточенных по мощной древовидной структуре. Вот и попробуй перебери их все!
Понятие X-сектора
: Открытие феномена X-секторов произошло совершенно случайно. В процессе разработки защитных механизмов, основанных на нестандартных форматах диска, автор стремился подобрать такие искажения, которые бы корректно обрабатывались всеми моделями приводов, но в тоже время не копировались ни одним из копировщиков. Эксперименты шли с переменным успехом и многие из защитных механизмов "понуро сходили с дистанции", не выдержав чудовищной жесткости испытаний. Одни из них оказывались слишком придирчивыми к оборудованию и на ряде проводов защищенные диски частично или полностью не читались; другие же копировались широко распространенными копировщиками Alcohol120% Алкоголь и Clone CDCloneCD…
Зачастую на защищаемых дисках проявлялись один или два сбойных сектора, поначалу списываемые на физические дефекты носителя. Однако, вскоре от этой гипотезы пришлось отказаться, поскольку сбойные сектора появлялись в строго определенном месте, каким при ближайшем рассмотрении и оказался предпоследний сектор области пост-зазораной области всякого трека. Да, именно тот самый сектор, чей заголовок был искажен!
Копирование X-секторов.: Теоретически, в копировании X-секторов нет ничего сложного. Достаточно подобрать современный читающий/пишущий софт, спроектированный с учетом требований "Оранжевой книги" и знающий о существовании блоков Run-in/Run-out блоков (т. е. обнуляющий тип сектора по двум младшем битам, а не по всему полю MODE целиком). Так же, копировщик должен аккуратно переносить содержимое областей Pre-gap и Post-gap областей с оригинала на копию, игнорируя "незлобное ворчание" стандарта по поводу, что никаких пользовательских данных здесь все равно нет.
Как уже говорилось ранеевыше, копировщик Clone CDCloneCD не удовлетворяет этому требованию —– дамп с защищенного диска он снимает вполне корректно, но с "проотжигом" последнего уже не справляется. Программа Alcohol 120% снимает корректный дамп, "прожигает" корректный диск, победоносно выплевывая работоспособный дубликат.
Разумеется, речь идет лишь о копировании "водяных знаков" в чистом виде, но даже незначительное усложнение защитного механизма повергает обоих копировщиков в глубокий ужас, граничащий с глубоким "расстройством их нервной системы"ой или попросту говоря —– "зависанием".
Поскольку Clone CDCloneCD более компетентен в снятии дампов, чем Alcohol 120%Алкоголь, для создания образа защищенного диска лучше всего использовать Clone CDCloneCD, а для "прожига" —– CDRWin или /Alcohol 120%Алкоголь. Если же это не поможет, —– хакеру придется выбирать между разработкой собственного копировщика и взломом непосредственного самого защитнщенного механизма.
Эксперименты с X-сектором. Любопытным побочным эффектом становится нечитабемльность предпоследнего сектора, ранее принадлежавшему Post-gap настоящего трека (его абсолютный адрес равен &Lead--Out —– 2), —– далее по тексту именуемым X-сектором. Попытка чтения данного сектора командой READ CD приводит к возврату нестандартных сообщений об ошибке, специфичных для каждой конкретной модели привода. ДалееНиже (листинг 6.67—6.69) приведеныа SENSE INFO приводов ASUS, NEC и TEAC. SENSE-INFO, возвращенная приводом ASUS, наиболее интересна (листинг 6.67). Привод "рапортует" об ошибке чтения, но не может вразумительно объяснить, что именно послужило первопричиной ее возникновения; первый байт SENSE-INFO, равный нулю, "торжественно заявляет", что никаких ошибок здесь вообще нет!:
Листинг 6.67. SENSE-INFO, возвращенная приводом ASUS
-ERR:00 00 00 00 00 00 00 00 00 00 00 00 00 00
SENSE-INFO, возращенная приводом NEC, более информативна (листинг 6.68). SENSE KEY, равный трем, указывает на ошибку типа "MEDIUM ERROR" (ошибка носителя); значения остальных полей — увы — нестандартны (на это указывает первый байт, равный F0h) и не могут быть расшифрованы по имеющимся у автора спецификациям.
Листинг 58 SENSE-INFO, возвращенная приводом ASUS, наиболее интересна; привод рапортует об ошибке чтения, но не может вразумительно объяснить, что именно послужило первопричиной ее возникновения; первый байт SENSE-INFO, равный нулю, торжественно заявляет, что никаких ошибок здесь вообще нет!
Листинг 6.68. SENSE-INFO, возращенная приводом NEC
-ERR:F0 00 03 00 00 00 12 0A 00 00 00 00 02 00
SENSE-INFO, возращенная приводом TEAC (листинг 6.69), так же указывает на "MEDIUM ERROR" (ошибка носителя), однако никаких других подробностей извлечь не удается.
Листинг 59 SENSE-INFO, возращенная приводом NEC, более информативна. SENSE KEY, равный трем, указывает на ошибку типа "MEDIUM ERROR" (ошибка носителя); значения остальных полей – увы – нестандартны (на это указывает первый байт, равный F0h) и не могут быть расшифрованы по имеющимся у автора спецификациям;
Листинг 6.69. SENSE-INFO, возращенная приводом TEAC
-ERR:F0 00 03 00 00 00 00 0A 00 00 00 00 11 00
Листинг 60 SENSE-INFO, возращенная приводом TEAC, так же указывает на "MEDIUM ERROR", однако никаких других подробностей извлечь не удается;
Зато позиционирование головки на X-сектор командой SEEK (2Bh) с последующим чтением содержимого Q-канала подкода командой READ SUBCHANNEL (42h) на всех доступных мне приводах проходит успешно.
Используя тот факт, что все известные мне копировщики лазерных дисков (включая такие программы как Clone CDCloneCD и Alcohol 120%) всегда читают субканальную информацию в общем потоке данных (т. е. получают ее посредством команды READ CD), прочитать субканальную информацию нечитаемого X-сектора они оказываются не в состоянии! Спрятав в Q-канал X-сектора ключевую метку, мы сможем легко отличить копию диска от его оригинала.
Подготовка образа защищенного диска выглядит так. Запустив любой HEX-редактор (например, HIEW) мы открываем IMAGE.SUB, содержащий субканальную информацию, и находим в нем абсолютный адрес X-сектора (в данном случае равный 00:29:31) и, убедившись, что это действительно абсолютный адрес, а не поле контрольной суммы (все поля абсолютных адресов расположены в файле IMAGE.SUB по смещению 0xxxxxx0Ch), каким-либо образом изменяем одно из полей Q-подканала данного сектора, не забыв соответствующим образом скорректировать и его контрольную сумму.
Проще всего просто переставить содержимое Q-?подканалов соседних секторов. Давайте, например, поменяем местами сектора 00:29:31 и 00:29:32 (листинг 6.70).:
Листинг 6.70. Содержимое оригинального IMAGE.SUB (местоположение будущей метки выделено полужирным шрифтом)
0003030C: 41 01 01 00 27 31 00 00 ¦ 29 31 8F AA 00 00 00 00 AOO '1 )1Пк
0003036C: 41 01 01 00 27 32 00 00 ¦ 29 32 51 1B 00 00 00 00 AOO '2 )2Q<
Листинг 61 содержимое оригинального IMAGE.SUB (местоположение будущей метки выделено жирным шрифтом)
Листинг 6.71. Содержимое "помеченного" IMAGE.SUB (метка выделена полужирным шрифтом)
0003030C: 41 01 01 00 27 32 00 00 ¦ 29 32 51 1B 00 00 00 00 AOO '2 )2Q<
0003036C: 41 01 01 00 27 31 00 00 ¦ 29 31 8F AA 00 00 00 00 AOO '1 )1_Є
Листинг 62 содержимое "помеченного" IMAGE.SUB (метка выделена жрным шрифтом)
Теперь запишим измененный образ на диск и убедимся, что субканальная метка действительно присутствует (листинг 6.72).:
Листинг 6.72. Ключевая метка в Q-канале X-сектора (выше) и
незащищенный диск (ниже)
>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 08 09 00 00 08 09 00 15 00 0C 01 14 01 01 00 00 08 09 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 09 00 00 08 08
Листинг 63 ключевая метка в Q-канале x-сектора (слева) и незащищенный диск (справа)
Смотрите, субканальная информация сектора 2056 (808h в шестнадцатеричной нотации), утверждает, что LBA-адрес данного сектора равен 809h (2057 в десятичной нотации), т. е. субканальная информация действительно искажена! Взяв для контраста любой незащищенный диск, мы убедимся, что его субканальная информация верна.
Как уже говорилось ранеевыше, копировщик Clone CDCloneCD не использует команду READ SUBCHANNEL, а получает субканальную информацию в общем потоке данных.
Столкнувшись с нечитабельным X-сектором, Clone CDCloneCD самостоятельно восстанавливает его субканальную информацию, такой, какой по его мнению она должна быть. Короче говоря, при копировании диска копировщиком Clone CDCloneCD наша ключевая метка "умирает" и Q-канал X-сектора теперь содержит "верные" данные.
Не верите —– смотрите сами (листинг 6.73).:
Листинг 6.73. Субканальная информация X-сектора оригинального диска (выше) и
его копии, полученной с помощью CloneCD (ниже)
>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 08 09 00 00 08 09 00 15 00 0C 01 14 01 01 00 00 08 09 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 09 00 00 08 08
Листинг 64 субканальная информация x-сектора оригинального диска (слева) и его копии, полученной с помощью Clone CD (справа)
Для получения более детальной информации давайте запустим Clone CDCloneCD и, убедившись что защищенный диск все еще находится в приводе, нажмем на "чтение CD в файл-образ". Субканальная информация, добытая Clone CDCloneCD существенно варьируется от одного привода к другому, но во всех случаях она оказывается неверна (сравните это с субканальной информацией, приведенной в листингахе $-6.67—6.69). Привод ASUS, не поддерживающий режим возвращения субканальных данных в общем потоке, заставил CloneCD их восстаналивать самостоятельно, использя для этой цели информацию из секторных заголовков. Как следсвтие — ключевую метку как "ветром сдуло" (листинг 6.74).
Листинг 6.74. Содержимое возвращенного Х-сектора, привод ASUS
000302AC: 41 01 01 00 27 31 00 00 ¦ 29 31 8F AA 00 00 00 00 AOO '1 )1Пк
0003030C: 41 01 01 00 27 32 00 00 ¦ 29 32 51 1B 00 00 00 00 AOO '2 )2Q<
Привод NEC на таком диске вообще "поехал крышей", возвращая "мусор" вместо субканальных данных (листинг 6.75).
Листинг 65 Привод ASUS, не поддерживающий режим возвращения субканальных данных в общем потоке, заставил Clone CD их восстаналивать самостоятельно, использя для этой цели информацию из секторных заголовков. Как следсвтие – ключевую метку как ветром сдуло.
Листинг 6.75. Содержимое возвращенного Х-сектора, привод NEC
000302AC: 01
01 01 00 00 00 00 00 ¦ 02 00 5A 28 00 00 00 00 OOO O Z(
0003030C: 01
01 01 00 00 00 00 00 ¦ 02 00 5A 28 00 00 00 00 OOO O Z(
Привод TEAC корректно возвратил субканальную информацию сектора (X-сектор+1), но конкретно "обломался" на самом X-секторе (листинг 6.76).
Листинг 66 Привод NEC на таком диске вообще поехал крышей, возвращая мусор вместо субканальных данных
Листинг 6.76. Содержимое возвращенного Х-сектора, привод TEAC
0003030C: 41 01 01 00 27 31 00 00 ¦ 29 31 8F AA 00 00 00 00 AOO '1 )1Пк
0003030C: 41 01 01 00 27 31 00 00 ¦ 29 31 8F AA 00 00 00 00 AOO '1 )1Пк
Листинг 67 Привод TEAC корректно возвратил субканальную информацию (x?сектор)+1 сектора, но конкретно обломался на самом x-секторе!
Таким образом, защита типа "фиктивный трек в Post-gap настоящего трека с меткой в Q-канале подкода предпоследнего сектора в Post-gap" (кодовое имя защиты : "Лиса") не копируется ни одним известным мне копировщиком лазерных дисков и в тоже время не конфликтует ни с каким доступным мне оборудованием (да она и не должна ни с чем конфликтовать!), поэтому "Лису" можно считать достаточно качественной и надежной защитой.
Тем не менее, не стоит переоценивать ее стойкость ко взлому. Взломать "Лису" не просто, а очень просто! Давайте, воспользовавшись парой команд SEEK и READ SUBCHANNAL, прочитаем субканальную информацию всего диска целиком и сверим ее с содержимым файла IMAGE.SUB, созданного Clone CDCloneCD или аналогичным ему копировщиком.Вообще-то, считывать всю субканальную информацию совершенно ни к чему, —– достаточно проверить на предмет "вшивости" одни лишь сбойные сектора —– те, что Clone CDCloneCD не смог прочесть. Обнаружив ключевую метку, просто скорректируйте соответствующие поля в файле IMAGE.SUB файле и "запишите" модифицированный образ на диск CD-R/CD-RW диск. Все! Теперь, с точки зрения защиты, копия и оригинал будет совершенно идентичны друг другу и "Лиса", "стыдливо махнув своим огненным хвостом, скроется в лесу"… Единственная проблема состоит в том, что команда READ SUBCHANNAL возвращает субканальную информацию в несколько кастрированном узезанном виде —– без следов наличия контрольной суммы[Y186] [n2k187] . и
Практические советы по восстановлению системы в "боевых" условиях
1.Во время исполнения ошибки имеют наивысший приоритет. Прервать исполнение ошибки может только другая, более активная ошибка.
2. Запросы операционной системы к ошибкам ошибками могут игнорироваться.
3. Запросы ошибок к операционной системе игнорироваться не могут.
4. При работе с файлами ошибки могут пользоваться файловой системой базовой ОС и ее ошибками.
5. На ЭВМ с параллельной архитектурой может выполняться несколько ошибок одновременно.
"Теория ошибок" В. Тихонов
При вставке диска в привод компьютер "зависает"
Вы вставляете диск в привод, привод раскручивает диск, интенсивно мигая индикатором активности и… зависает, зачастую "подзавешивая" вместе с собой и операционную систему. В легких случаях положение спасает кнопка Eject, в тяжелых–— Reset.
Такое поведение характерно для защищенных дисков, защита которых основана на искаженном TOC'e. Большинство приводов к искаженному TOC'у относятся довольно лояльно (хотя это смотря еще что искажать), но встречаются и такие, которые при этом просто виснут. Если прочесть защищенный диск все же необходимо –— попробуйте сменить привод.
Другой возможный вариант –— зацикленная файловая система. При "прожиге" дисков CD-R/CD-RW "дисков кривым" софтом (software) –— такое часто случается. Удерживая клавишу <Shift> во время загрузки диска запретите операционной системе читать его содержимое (или же просто временно отключите автозапуск) и посредством той же утилиты ISO 9660.dir вытяните изх диска все, что только с него можно вытянуть.
Приложения, недопустимые операции и все, -все, -все…
Низкоуровневая работа с оборудованием, требует чрезвычайной собранности и внимания. Малейшая ошибка приводит к появлению "голубого экрана смерти" (BSOD — Blue Screen Of Death) или аварийному завершению одного или нескольких приложений. У разработчиков драйверов и саперов есть много общего –— ни та, ни другая профессия беспечности не прощает. Интерфейсы ASPI и SPTI, несмотря на свои "высококровные" оболочки (в просторечии именуемые "враперрами" от англ. wrapper — упаковщик[n2k85] обертка) настроены столь же агрессивно и "роняют" систему по поводу и без. Пройдет немало времени, прежде чем вы научитесь писать стабильный и неприхотливый код, а до той поры залогом вашего выживания будет умение бороться с последствиями критических ошибок и различноговсякого рода сбоев.
Различные операционные системы по разному реагируют на критические ошибки. Так, например Windows NT резервирует два региона своего адресного пространства для выявления некорректных указателей. Один находится на самом "дне" карты памяти и предназначен для отлавливания нулевых указателей. Другой расположен между "кучей" и областью памяти, закрепленной за операционной системой. Он контролирует выход за пределы пользовательской области памяти и, вопреки расхожему мнению, никак не связан в функцией WriteProcessMemory (см. техническую заметку ID: Q92764 в MSDN). Оба региона занимают по 64 Кбайт, и всякая попытка доступа к ним расценивается системой, как критическая ошибка. В Windows 9x имеется всего лишь один регион в 4 Кбайта регион, следящий за нулевыми указателями, поэтому по своим контролирующим способностям она значительно уступает Windows NT.
В Windows NT экран критической ошибки (см. рис. 3.12) содержит следующую информацию:
q а)адрес машинной инструкции, возбудившей исключение;
q б)словесное описание категории исключения (или его код, если категория исключения неизвестна);
q в)параметры исключения ( адрес недействительной ячейки памяти, род операции и т. д.).
Рис.унок 3.21. test_2000 Сообщение о критической ошибке, выдаваемое операционной системой Windows 2000
Операционные системы семейства Windows 9x в этом отношении намного более информативны (см. рис. 3.2) и помимо категории исключения выводят содержимое регистров ЦП на момент сбоя, состояние стека и байты памяти по адресу CS:EIP (т. е. текущему адресу исполнения). Впрочем, наличие "Доктора Ватсона" (о нем –— далеениже) стирает различие между двумя системами, и потому можно говорить лишь об удобстве и эргономике Windows 9x, сразу предоставляющей весь минимум необходимых сведений, в то время как в Windows NT отчет об ошибке создается отдельной утилитой.
Рис.унок 3.3 2. Test_98 Сообщение о критической ошибке, выдаваемое операционной системой Windows 98
Если никакой из отладчиков в системе не установлен, то окно о критической ошибке имеет всего лишь одну кнопку –— кнопку "ОК", нажатие которой приводит к аварийному закрытию "политнекорректного" приложения. При желании окно критической ошибки можно оснастить кнопкой "Отмена" ("Cancel"), запускающей отладчик или иную утилиту анализа ситуации. Важно понять, что кнопка "Отмена" отнюдь не отменяет автоматическое закрытие приложения, но при некоторой сноровке вы можете устранить "пробоину" вручную, продолжив нормальную работу (см. врезку[Y86] ).
Запустите "Редактор реестра" (Regedit) и перейдите в раздел "HKLM\SOFTWARE\
Microsoft\Windows NT\CurrentVersion\AeDebug". Если такого раздела нет, то – создайте его самостоятельно. Строковой параметр "Debugger" задает путь к файлу отладчика со всеми необходимыми ключами; строковой параметр "Auto" указывает, должен ли отладчик запускаться автоматически (значение "1") или предлагать пользователю свободу выбора ("0").Наконец, двойное слово параметра "UserDebuggerHotKey" специфицирует скэн-код "горячей" клавиши для принудительного вызова отладчика.
Пример реализации защиты на программном уровне
Покажем теперь как такая защита может быть реализована на программном уровне. Самое простое, что можно сделать–— отправить приводу команду "сырого" чтения TOC: READ TOC (opcode: 43h, format: 2h) и сравнить возращенный ею результат с эталоном. Какие именно поля TOC защита будет проверять –— это ее личное дело. По минимуму достаточно проверить количество сессий и стартовый адрес искаженного трека. По максимуму можно контролировать весь TOC целиком. Естественно, от побайтового сравнения контролируемого TOC с оригиналом настоятельно рекомендуется воздержаться, –— т. к. это неявно закладывает защиту на особенности микропрограммной прошивки читающего привода. Стандарт ничего не говорит том, в каком порядке должно возвращается содержимое TOC и потому его бинарное представление может варьироваться от привода к приводу (хотя на практике такого и не наблюдается). Грамотно спроектированная защита должна анализировать только те поля, к содержимому которых она привязывается явно.
Демонстрационный пример, приведенный в листинге 6.8ниже, как раз и иллюстрирует технику корректной привязки к TOC. Разумеется, явная проверка целости TOC может быть элементарно обнаружена хакером (hacker) и выкинута из программы как ненужная, поэтому не стоит копировать этот демонстрационный пример один к одному в свои программы. Лучше используйте значения полей TOC как рабочие константы жизненно необходимые для нормальной работоспособности программы, –— в этом случае сличение "паспортов с лицами" будет не столь наглядным. Естественно, явная проверка оригинальности диска все равно обязана быть, но ее основная цель отнюдь не защитить программу от взлома, а довести до сведения пользователя, что проверяемый диск с точки зрения защиты не является лицензионным.
Листинг 6.8. [crackme.9822C095h.c] Демонстрационный пример простейшей защиты, привязывающейся к искаженному TOC и не позволяющей себя копировать
/*----------------------------------------------------------------------------
Примеры исследования реальных программ
В качестве закрепления всего ранеевышесказанного и обретения минимальных практических навыков давайте исследуем несколько популярных программ, работающих с лазерными дисками на низком уровне, на предмет выяснения: как именно осуществляется такое взаимодействие.
Вызвав незаменимый отладчик Soft-Ice и установив точку останова на "bpxCreateFileA if (*esp->4=='\\\\.\\')", мы будем последовательно запускать три следующих программы: Alcohol 120%, Easy CD Creator и Clone CDCloneCD, каждый раз отмечая имя открываемого устройства. Итак, приступим.к…
Принудительный выход из функции
Запускаем тестовую программу, набиваем в одном или нескольких окнах какой-нибудь текст, затем в меню "Help"
выбираем пункт "About TestCEdit"
и в появившемся диалоговом окне щелкаем по кнопке "make error". Опля! Программа выбрасывает критическую ошибку и, если мы нажмем на "ОК", все не сохраненные данные необратимо погибнут, что никак не входит в наши планы. Однако при наличии предварительно установленного отладчика мы еще можем кое-что предпринять. Пусть для определенности это будет Microsoft Visual Studio Debugger.
Нажимаем кнопку "Отменау" и отладчик немедленно дизассемблирует функцию, возбудившую исключение (см. листинг 3.5, приведенный ниже).[Y90] [n2k91]
Листинг 3.5. Отладчик Microsoft Visual Studio Debugger дизассемблировал функцию, возбудившую исключение
0040135C push esi
0040135D mov esi,dword ptr [esp+8]
00401361 push edi
00401362 movsx edi,byte ptr [ecx+esi]
00401366 add eax,edi
00401368 inc ecx
00401369 cmp ecx,edx
0040136B jl 00401362
0040136D pop edi
0040136E pop esi
0040136F ret 8
Листинг 5 отладчик Microsoft Visual Studio Debugger дизассемблировал функцию, возбудившую исключение
Проанализировав причину возникновения исключения (функции передан указатель на невыделенную память), мы приходим к выводу, что заставить функцию продолжить свою работу невозможно, поскольку структура передаваемых данных нам неизвестна. Приходится прибегать к принудительному возврату в материнскую функцию, не забыв при этом установить флаг ошибки, сигнализируя программе, что текущая операция не была выполнена. К сожалению, никаких общепринятых флагов ошибок не существует, и различные функции используют различные соглашения.
Чтобы выяснить, как обстоят дела в данном конкретном случае, мы должны дизассемблировать материнскую функцию и определить какой именно код ошибки она ожидает.
Переместив курсор в окно дампа, набьем в строке адреса название регистра указателя вершины стека –— "ESP" и нажмем на клавишу <Enter>. Содержимое стека тут же предстанет перед нашими глазами (листинг 3.6).:
Листинг 3.6. Поиск адреса возврата из текущей функции (выделен полужирным шрифтом)
0012F488 0012FA64 0012FA64 004012FF
0012F494 00000000 00000064 00403458
0012F4A0 FFFFFFFF 0012F4C4 6C291CEA
0012F4AC 00000019 00000000 6C32FAF0
0012F4B8 0012F4C0 0012FA64 01100059
0012F4C4 006403C2 002F5788 00000000
0012F4D0 00640301 77E16383 004C1E20
Листинг 6 поиск адреса возврата из текущей функции (выделен жирным шрифтом)
Первые два двойных слова соответствуют машинным командам POP EDI/POP ESI и не представляют для нас совершенно никакого интереса. А вот следующее двойное слово содержит адрес выхода в материнскую процедуру (в приведенном выше листинге 3.6 оно выделено полужирным шрифтом). Как раз его-то нам и надо!
Нажимаем <Ctrl>+<-D> и затем 0x4012FF, отладчик послушно отображает следующий дизассемблерный текст (листинг 3.7).:
Листинг 3.7. Дизассемблерный листинг материнской функции
004012FA call 00401350
004012FF cmp eax,0FFh
00401302 je 0040132D
00401304 push eax
00401305 lea eax, [esp+8]
00401309 push 405054h
0040130E push eax
0040130F call dword ptr ds:[4033B4h]
00401315 add esp, 0Ch
00401318 lea ecx, [esp+4]
0040131C push 0
0040131E push 0
00401320 push ecx
00401321 mov ecx, esi
00401323 call 00401BC4
00401328 pop esi
00401329 add esp,64h
0040132C ret
0040132C
0040132D push 0
0040132D ; эта ветка получает управление, если Функция 401350h вернет FFh
0040132F push 0
00401331 push 405048h
00401336 mov ecx,esi
00401338 call 00401BC4
0040133D pop esi
0040133E add esp,64h
00401341 ret
Листинг 7 дизассемблерный листинг материнской функции
Смотрите: если регистр EAX равен FFh, то материнская функция передает управление на ветку 40132Dh и спустя несколько машинных команд завершает свою работу, передавая бразды правления функции более высокого уровня. Напротив, если EAX != FFh, то его значение передается функции 4033B4h. Следовательно, мы можем предположить, что FFh –— это флаг ошибки и есть. Возвращаемся в подопытную функцию, нажав <Ctrl>+<-G> и "EIP", переходим в окно "Registers" и меняем значение регистра EAX на FFh.
Теперь необходимо найти подходящую точку возврата из функции. Просто перейти к машинной команде "RET" нельзя, поскольку перед выходом из функции следует в обязательном порядке сбалансировать стек или нас "выбросит" неизвестно куда и программа "обрушится" окончательно.
В общем случае число PUSH-команд должно в точности соответствовать количеству POP (также учитывайте, что PUSH DWORD X эквивалентен SUB ESP, 4, а POP DWORD X –—
ADD ESP, 4). Проанализировав дизассемблерный листинг функции, мы приходим к выводу, что для достижения "гармонии добра и зла" мы должны "стащить" с вершины стека два двойных слова, соответствующие машинным командам 40135С:PUSH ESI и 401361:PUSH EDI. Это достигается передачей управления по адресу 40136Dh, где "живут" дваа "добродушныхех" команды POP'a'а, приводящие стек в равновесное состояние.Подводим сюда курсор и уверенным щелчком правой клавиши мыши вызываем контекстное меню, среди пунктов которого выбираем "Set Next Statement". Как вариант можно перейти в окно регистров и изменить значение регистра EIP с 401362h на 40136Dh.
Нажатием клавиши <F5> мы заставляем процессор продолжить выполнение программы и… о чудо! Она действительно продолжает свою работу ("незлобное ругательство" на ошибку последней операции –— не в счет!). Несохраненные данные спасены!
Программная, вводная и выводная области, оглавление диска и область данных
Lead-in area, data area, Lead-out area и TOC
Последовательность секторов одного формата объединяется в дорожку (иначе трек (track)), минимально возможная длина которой составляет 300 секторов, а максимальная —– весь диск целиком. Первый и последний треки диска, т. е. (вводнаяLead-In (Lead-In) и выводная (Lead-Out) области соответственно,) используются для служебных целей, хотя большинство современных приводов способны обходится и без них (а пишущие приводыписцы это делать вообще обязаны).
Вводная область диска
Lead-In Area —– (Lead-In areaвводная область диска). Служебная область диска (рис. 1.24) по сути своей представляющая нулевой трек, всегда предшествующий первому треку PMA[Y52] [n2k53] . Каждая сессия многосессионного диска имеет собственную вводную область. Размер вводной области по стандарту составляет 9 Ммегабайт (60 секунд или 4500 секторов). Q-канал подкода вводной сессии содержит оглавление диска (TOC), среди прочей полезной информации указывающей либо на адрес выводной области (закрытый диск), либо на адрес вводной области следующей сессии (открытый диск). Содержимое вводной области недоступно для чтения на программном уровне (доступно у приводов MSI). Визуально вводная область выглядит равномерно освещенным блестящим кольцом.
___Внимание!
Не всякое блестящее кольцо это область Lead-In! Настоящая областьий Lead-In всегда находится на расстоянии 23 мм от края диска, ав перед нейним идет еще всякий "мусор"ая срань.
Рис. 1.24. унок 24 0х011 Строение лазерного диска
Выводная область диска —
Lead-Out Area – (Lead-Out areaвыводная область диска). Служебная область диска, условно обозначаемая треком номер AAh и замыкающая собой всякую закрытую сессию. Выводная область служит своеобразным индикатором конца сессии и/или диска и помогает оптической головке не вылететь за пределы диска. Пишущие приводы должны корректно обрабатывать диски с незакрытыми сессиями, однако, обыкновенные приводы CD-ROM и аудио проигрыватели это делать не обязаны.
Внимание!
Отсутствие выводной сессии (равно как и некорректное задание ее адреса) может повредить некоторые модели приводов (один из них PHILIPS).
Емкость выводной области одно-сессионногоодно-сессионного диска по стандарту составляет 13,.5 МбайтMB (6750 секторов или 1,.5 минуты). Емкость выводных областей для второй и последующих сессий многосессионных дисков уменьшена до 4 МбайтБ (0,.5 минуты или 2250 секторов). Содержимое выводной области недоступно на программном уровне (доступно у приводов MSI). Визуально выводная область выглядит равномерно освещенным блестящим кольцом.
Оглавление диска или иначе таблица содержимого
TOC – Table Of Content (TOC — Table Of ContentТаблица Содержимого или попросту оглавление диска). Служебная область диска, записанная в Q-канале подкода вводной области диска, так же называемой областью Lead-In областью (такое блестящее кольцо у внутреннего края диска). Многосессионный диск имеет несколько независимых TOC —– по одному TOC'у на каждую закрытую сессию. TOC незакрытой сессии храниться в специальной области в PMA и по стандарту доступен лишь пишущим приводам, однако, некоторые модели приводов CD-ROM приводов так же могут считывать TOC из PMA.
TOC содержит информацию о стартовых адресах вводной/выводной областей диска и атрибуты всех его треков (как-то тип трека: аудио или данные, а если данные то в каком режиме —– Mode 1, Mode 2 и т. д., абсолютном стартовом адресе трека и номере соответствующей ему сессии). Так же TOC содержит часть ATIP [Y54] [n2k55] и указатели на местоположения ее продолжения.
Непосредственно (т. е. на секторном уровне) для чтения TOC недоступен, но для извлечения его содержимого в "сыром" виде можно воспользоваться следующей SCSI/ATAPI командной READ TOC/PMA/ATIP (операционный код: 43h) с format field == 2h.
Однако не стоит путать TOC с файловой системой —– между ними нет ничего общего! Файловые системы лазерных дисковах хранятся непосредственно в PMA и свободно доступы для чтения на секторном уровне.
Программная область
Program Area – — (Program areaпрограммная область). Область диска, расположенная между областями Lead-In и Lead-Out областями и содержащая информационные треки с музыкой или данными. Это – основная область диска, целиком доступная на секторном уровне с паузами между аудио треками включительно. Подавляющее большинство цифровых CD данных (Data-CD) содержат один-единственный трек данных, хранящий в себе всю необходимую информацию, записанную в той или иной файловой системе. Впрочем, файловые системы лежат за гранью темы нашего разговора. Что же касается аудио CD (Audito-CD), то никакой файловой системы они не имеют, а используют для этой цели TOC, помещая каждую песню в отдельный трек.
Если за областью Lead-Out -областью располагается область Lead-In область, то такой диск называется многосессионным (multi-session). Каждая закрытая сессия имеет собственные областий Lead-In, Lead-Out и TOC, причем, указатель на выводную область, находящийсясодержащийся в TOC, может содержатьсодержать как действительный адрес выводной области текущей сессии, так и адрес вводной области следующей сессии! Количество сессий в принципе неограниченно, однако, в силу сквозной нумерации треков, количество сессий не может превышать 99. Сессия может быть независимой (TOC указывает только на треки внутри сессии) или связанной (TOC содержит адреса треков из предыдущих сессий). Однако, далеко не все приводы "знают" о существовании сессий. В частности, подавляющее большинство Audio-проигрывателей "видят" только первую сессию диска и игнорируют все остальные. "Благодаря" этому обстоятельству существует возможность создания дисков, не читающихся на компьютерных приводах CD-ROM'ах, но нормально "перевариваемых" CD-плейерами.
Сессия называется закрытой, если ее область данных обрамлена вводной и выводной областью. Незакрытые сессии могут читаться только устройствами записи (необходим доступ к PAMA). Указатель в TOC сессии на выводную область может содержать либо действительно адрес выводной области данной сессии (закрытый диск), либо адрес вводной области следующей сессии.Запись ограничивается местом на диске, местом в PMA и числом треков (треки имеют сквозную нумерации по всему диску от 01 до 99).. Сессии могут быть связаны также на уровне файловой системы. Механизм сессий позволяет "изменять" информацию на болванке CD-R, дописывая новую сессию.
Простейшие практические реализации
Хорошим примером воплощения кодера/декодера Рида-Соломона являются "древние" модели жестких дисков, разработанных в недрах фирмы IBM. Модель IBM 3370
имела простой и наглядный кодер/декодер Рида-Соломона типа (174,171) в поле Галуа GF(256). Другими словами, он оперировал 8-битными ячейками (28 = 256), и на 171 информационный байт приходилось 3 байта суммы четности, что в результате давало кодовое слово с размером 174 байт, причем, как мы увидим далее, все три байта контрольной суммы рассчитывались совершенно независимо друг от друга, поэтому фактически кодер/декодер Рида-Соломона оперировал одним байтом, что значительно упрощало его архитектуру.
В современных же винчестерах кодер/декодер Рида-Соломона стал слишком "навороченным", а количество контрольных байтов многократно возросло, в результате чего пришлось работать с числами противоестественных разрядностей (порядка 1408 бит и более). Как следствие — программный код "ощетинился" толстым слоем дополнительных проверок, циклов и функций, чрезвычайно затрудняющих его понимание (к тому же большинство производителей железа в последнее время перешли на аппаратные кодеры/декодеры Рида-Соломона, полностьюцеликом реализованные в одной микросхеме). В общем, прогресс — прогрессом, а для изучения базовых принципов работы лучше использовать "древние" модели.
Ниже (листинг 21.17 и 21.18) приведены два фрагмента оригинальной прошивки жесткого диска IBM 3370 (только не спрашивайте: откуда они у меня взялисья).:
Листинг 21.17. Ключевой фрагмент кодера Рида-Соломона, вырванный из прошивки IBM 3370
for (s0 = s1 = sm1 = i = 0; i < BLOCK_SIZE; ++i)
{
s0 = s0 ^ input[i];
s1 = GF_mult_by_alpha[ s1 ^ input[i] ];
sm1 = GF_mult_by_alpha_inverse[sm1 ^ input[i] ];
};
Листинг 21.18. Ключевой фрагмент декодера Рида-Соломона, вырванный из IBM 3370
err_i = GF_log_base_alpha[ GF_divide[s1][s0] ]; // вычисляем синдром ошибки
input[err_i] ^= s0; // исправляем сбойный байт
Ну, что, слабо нам разобраться: как он работает? Что касательно переменной s0 — с ней все предельно ясно: она хранит контрольную сумму, рассчитанную по тривиальному алгоритму. Как вывы, наверноенаверное, помните, сложение в полях Галуа осуществляется логической операцией XOR (исключающее ИЛИ) и потому: s0 += input[i[Y68] [n2k69] ].
Назначение переменной s1 выяснить сложнее и чтобы понять суть разворачивающегося вокруг нее "метаболизма", мы должны знать содержимое таблицы GF_mult_by_alpha. Несмотря на то, что по соображениям экономии бумажного пространства она здесь не приводится, ее имя говорит само за себя: содержимое переменной s1 суммируется с очередным байтом контролируемого потока данных и умножается на так называемый примитивный член, обозначаемый как alpha, и равный двум. Другими словами: s1 = 2 * (s1 + input[i]).
Допустим, один из байтов потока данных в последствии будет искажен (обозначим его позицию как err_i), тогда индекс искаженного байта можно определить тривиальным делением переменной s1 на s0. Почему? Так ведь выражение s1 = 2 * (s1 + input[i]) по своей сути есть ни что иное, как завуалированное умножение информационного слова на порожденный полином, динамически генерируемый на основе своего примитивного члена alpha. А контрольная сумма информационного слова, хранящаяся в переменной s0, фактически представляет собой то же самое информационное слово, только представленное в более "компактной" форме. И, как уже говорилось в предыдущей главе: если ошибка произошла в позиции x, то остаток от деления кодового слова на порожденный полином будет равен k = 2x. Остается лишь по известному значению k
вычислить x, что в данном случае осуществляется путем обращения к таблице GF_log_base_alpha, хранящей пары соответствий между k и 2x. Коль скоро позиция сбойного байта найдена, его можно исправить путем выполнения операции XOR (исключающее ИЛИ)'а с рассчитанной контрольной суммой s0 (input[err_i] ^= s0).
Конечно, сказанное справедливо только для одиночных ошибок, а искажения двух и более байт на один блокблок, данный алгоритм исправить не в силах. Собственно, для этого и присутствует третий байт контрольной суммы — sm1, — защищающий декодер от "политнекорректных" попыток исправления ошибок, когда их больше одной. Если выражение s1/s0 == sm1 * s0
становится ложным, — контроллер винчестера может засвидетельствовать факт наличия множественных ошибок, констатируя невозможность их исправления.
Однако, как хорошо известно, дефекты магнитной поверхности имеют тенденцию образовывать не одиночные, а групповые ошибки. И, чтобы хоть как-то компенсировать слабость корректирующего алгоритма, парни из фирмы IBM прибегли к чередованию байт. Винчестер IBM 3370 имел чередование 3:1, означающее то, чтото есть сначала шел первый байт первого блока, за ним первый байт второго блока, за ним — первый байт третьего и только потом — второй байт первого блока. Такой трюк усиливал корректирующую способность винчестера с одной одиночной ошибки, до трех последовательно искаженных байт.. Однако, если разрушению подвергались не соседние байты, то корректирующая способность вновь уменьшаласьопускалась до значений в один искаженный байт на блок, но вероятность такого события была несравненно меньше.
Естественно, что данный алгоритм может быть реализован не только в самом жестком диске, но и вне его. Варьируя размер блоков и степень чередования, вы обеспечите себе лучшую или худшую защищенность при большей или меньшей избыточности информации. Действительно, пусть у нас есть N
секторов на диске. Тогда, разбив их на блоки по 174 сектора в каждом и выделив 3 сектора для хранения контрольной суммы, мы сможем восстановить по меньшей мере N/174 секторов диска. Исходя из средней емкости диска в 100 Гбайт (что соответствует 209 .715 .200 секторам), мы сможем восстановить до 1 .205 .259 секторов даже при их полном физическом разрушении, затратив всего лишь 2% дискового пространства для хранения контрольных сумм.
Согласитесь, что очень редко, когдаая процесс "осыпанияка" винчестера проходит столь стремительно, чтобы корректирующих способностей кода Рида-Соломона оказалась недостаточно для егое воскрешения. (конечноКонечно, если этоу "осыпаниеку" вовремя заметить и, если коэффициент чередования выбран правильно: так, что сектора, принадлежащие одному дисковому блину, обслуживались бы разными корректирующими блоками, в противном случае при повреждении поверхности одного из блинов возникнет групповая ошибка, уже неисправимаянеисправляемая данной программой).
А как быть, если выйдет из строя"навернется" весь жесткий диск целиком? Наиболее разумный выход — создать массив из нескольких дисков, хранящих полезную информацию вперемешку с корректирующими кодами. Главный минус такого подхода — его неэффективность на массивах, состоящих из небольшого количества жестких дисков. Разумный минимум — это: четыре информационных диска и один контрольный, тогдакогда потеря любого из информационных дисков компенсируется оставшихсяоставшимся "в живых" контрольным. Ну, а потерянный контрольный диск элементарным образом заменяется на новый, с последующим пересчетом всех контрольныхе кодов. Правда, одновременный выход двух дисков из строя — это "кранты". Массив из пятнадцати дисков RAID (Redundant Array of Independent Disks, матрица независимых дисковых накопителей с избыточностью), двенадцать из которых — информационные, а оставшиеся три — контрольные, намного более отказоустойчив и допускает одновременный крах двух любых дисков, а при благоприятном стечении обстоятельств — и трех.
Собственно, во всем этом ничего нового нет, и соответствующие RAID-контроллеры можно купить буквально в любом магазине. Однако… мне трудно представить себесебе, сколько будет стоить RAID-контроллер уровня 15 и удастся ли его вообще заставить работать (по личному опыту могу сказать, что RAID-контроллеры даже начальных уровней — вещь крайне "глючная", капризная и требовательная как к "железу", так и к операционному окружению).
Наконец, практически все RAID- контроллеры требуют наличия абсолютно идентичных, ну или близких по своим характеристикам и/или интерфейсам дисков. А коли таковых нет?
Программный RAID, активно пропагандируемый настоящим автором, всех этих недостатков лишен. Вы можете использовать диски различной геометрии и даже различной емкости, причем никто не обязывает вас сосредотачивать их в одном месте — доступ к дискам может осуществляться и по сети, причем совершенно необязательно отводить под RAID-хранилище весь диск целиком! Вы вольны произвольным образом выделять ту или иную часть дискового пространства.
Как это можно реально использовать на практике? Первое, что приходит на ум, использовать часть емкости жестких дисков под хранение избыточной информации, помогающей восстановить их в случае аварии. Если несколько компьютеров объединить в сеть (что уже давным-давно сделано и без нас), то при относительно небольших накладных расходах мы сможем восстановить любой из жестких дисков членов сети даже при полном его разрушении лишь за счет одной избыточной информации, распределенной между остальными компьютерами. Более надежного хранилища для ваших данных нельзя и придумать! Подобная схема была реализована автором в локальных сетях нескольких фирм и доказала свою высокую живучесть, гибкость и функциональность. Необходимость в постоянном резервировании содержимого жестких дисков автоматически отпала, что в условиях одно-ранговой сети с отсутствующим выделенным сервером более, чем актуально! А ведь такие локальные сети — не редкость (нет, я не утверждаю, что такие сети хороши, просто я констатирую факт, что они существуют в природе и в обозримом будущем "вымирать" не собираются).
Единственный минус программного RAID'а'а — его невысокая производительность. В частности, поставив программный RAID на сервер, обрабатывающий тысячи запросов ежесекундно и интенсивно модифицирующий большое количество файлов, вы не выиграете ничего, но… ведь само понятие "производительности" очень относительно и при достаточно быстром процессоре кодирование/декодирование информации вполне реально осуществлять и "на лету" безо всяких потерь в пропускной способности! С другой стороны, если операции чтения доминируют над операциями записи, то ставить программный RAID сам "Крестный Отец" велел, поскольку контроль целостности считываемой информации осуществляется на "железном" уровне самим приводом и при использовании систематического кодирования (т. е.информационные слова — отдельно, байты четности — отдельно), декодеру Рида-Соломона нет никакой нужды как-то вмешиваться в этот процесс и его помощь требуется лишь тогда, когда часть информации оказывается безнадежно разрушена, что случается прямо-таки скажем не часто. Так что, право же, не стоит "перекармливать" фирмы, специализирующие на выпуске RAID'ов, тем более, что на домашний и мелко-офисный рынок они все равно не обращают внимания.
"Раскрутка" стека
Далеко не во всех случаях принудительный выход из функции оказывается возможным. Ряд критических сбоев затрагивает не одну, а сразу несколько вложенных функций, и тогда для реанимации программы мы должны совершить глубокий откат назад, продолжив выполнение программы с того места, где бы ее работоспособности ничто не угрожало. Точная глубина отката подбирается экспериментально и обычно составляет три–— пять ступеней. Имейте ввиду, что если вложенные функции модифицируют глобальные данные (например данные кучи), то попытка отката может привести к полному краху отлаживаемой программы, поэтому требуемую глубину отката желательно угадать с первого раза, придерживаясь правила: "лучше перебрать, чем недобрать". С другой стороны, чрезмерно глубокий откат ведет к потере всех не сохраненных данных…
Процедура отката состоит из трех шагов: а)
q построения дерева вызовов;
q б) определения координат стекового фрейма для каждого из них; в)
q восстановления регистрового контекста материнской функции.
Хороший отладчик все это сделает за нас, и вам останется лишь записать в регистры EIP и ESP соответствующие значения. К сожалению, отладчик Microsoft Visual Studio Debugger к хорошим отладчикам не относится. Он довольно посредственно трассирует стек, пропуская FPO-функции (Frame Point Omission — – функции с оптимизированным фреймом) и не сообщает координат стекового фрейма, "благодаря" чему самую трудоемкую часть работы нам приходится выполнять самостоятельно.
Впрочем, даже такой стек вызовов все же лучше, чем совсем ничего. Раскручивая его вручную мы будем отталкиваться от того, что координаты фрейма естественным образом определяются по адресу возврата. Допустим, содержимое окна "Call Stacks"
выглядит так как это показано в листинге 3.8.:
Листинг 3.8. Содержимое окна Call Stacks отладчика Microsoft Visual Studio Debugger
TESTCEDIT! 00401362()
MFC42! 6c2922ae()
MFC42! 6c298fc5()
MFC42! 6c292976()
MFC42! 6c291dcc()
MFC42! 6c291cea()
MFC42! 6c291c73()
MFC42! 6c291bfb()
MFC42! 6c291bba()
Листинг 8 содержимое окна Call Stacks отладчика Microsoft Visual Studio Debugger
Попробуем найти в стеке адреса 6C2922AEh и 6C298FC5h, соответствующие двум последним ступеням исполнения. Нажимаем <ATL>+<-6> для перехода в окно дампа и, воспользовавшись "горячей" комбинацией клавиш <Ctr> +<l-G> в качестве базового адреса отображения, выбираем "ESP". Прокручивая окно дампа вниз, мы обнаруживаем оба адреса возврата (в приведенном далеениже листинге 3.9 они выделены рамкой).:
Листинг 3.9. Содержимое стека после "раскрутки"
0012F488 0012FA64 0012FA64 004012FF ß 0040136F:ret 8 первый адрес возврата
0012F494 00000000 00000064 00403458 ß 00401328:pop esi
0012F4A0 FFFFFFFF 0012F4C4 6C291CEA
0012F4AC 00000019 00000000 6C32FAF0
0012F4B8 0012F4C0 0012FA64 01100059
0012F4C4 00320774 002F5788 00000000
0012F4D0 00320701 77E16383 004C1E20
0012F4DC 00320774 002F5788 00000000
0012F4E8 000003E8 0012FA64 004F8CD8
0012F4F4 0012F4DC 002F5788 0012F560
0012F500 77E61D49 6C2923D8 00403458 ß 0040132C:ret;
0012F50C 00000111 0012F540 6C2922AE ß6C29237E:pop ebx/pop ebp/ret 1Ch
0012F518 0012FA64 000003E8 00000000
0012F518 0012FA64 000003E8 00000000
0012F524 004012F0 00000000 0000000C
0012F530 00000000 00000000 0012FA64
0012F53C 000003E8 0012F564 6C298FC5
0012F548 000003E8 00000000 00000000
0012F554 00000000 000003E8 0012FA64
Листинг 9 содержимое стека после раскрутки
Ячейки памяти, лежащие выше адресов возврата, представляют собой значения регистров, сохраненные в стеке при входе в функцию и восстанавливаемые при ее завершении. Ячейки памяти, лежащие ниже адресов возврата, "оккупированы" аргументами функции (если, конечно, у функции есть аргументы), или же принадлежат локальным переменным материнской функции, если дочерняя функция не принимает никаких аргументов.
Возвращаясь к листингу 3.5, отметим, что два двойных слова, лежащие на верхушке стека, соответствуют машинным командам POP EDI и POP ESI, а следующий за ними адрес –— 4012FFh –— это тот самый адрес, управление которому передается командой 40136Fh:RET 8. Для продолжения раскрутки стека мы должны дизассемблировать код по этому адресу (листинг 3.1:0).
Листинг 3.10. Дизассемблерный листинг праматеринской функции ("бабушки")
004012FA call 00401350
004012FF cmp eax,0FFh
00401302 je 0040132D
00401304 push eax
00401305 lea eax,[esp+8]
00401309 push 405054h
0040130E push eax
0040130F call dword ptr ds:[4033B4h]
00401315 add esp,0Ch
00401318 lea ecx,[esp+4]
0040131C push 0
0040131E push 0
00401320 push ecx
00401321 mov ecx,esi
00401323 call 00401BC4
00401328 pop esi
00401329 add esp,64h
0040132C ret ; SS:[ESP] = 6C2923D8
Листинг 10 дизассемблерный листинг праматеринской функции ("бабушки")
Прокручивая экран вниз, мы замечаем инструкцию ADD ESP, 64, закрывающую текущий кадр стека. Еще восемь байт снимает инструкция 40136Fh:RET 8 и четыре байта оттягивает на себя 401328:POP ESI. Таким образом, позиция адреса возврата в стеке равна: current_ESP + 64h + 8 + 4 == 70h. Спускаемся на 70h байт ниже и видим адрес возврата из праматеринской функции (листинг3.11).:
Листинг 3.12. Адрес возврата из праматеринской функции
0012F500 77E61D49 6C2923D8 00403458 ß
00401328:POP ESI/ret;
Листинг 11 адрес возврата из праматеринской функции
Первое двойное слово –— это значение регистра ESI, который нам предстоит вручную восстановить; второе –— адрес возврата из функции. Нажатием <Ctrl>+<-G>, "0x6C2923D8" мы продолжаем "раскручивать" стек (листинг 3.12).:
Листинг 3.12. Дизассемблерный листинг прапраматеринской функции
6C2923D8 jmp 6C29237B
…
6C29237B mov eax,ebx
6C29237D pop esi
6C29237E pop ebx
6C29237F pop ebp
6C292380 ret 1Ch
Листинг 12 дизассемблерный листинг пра-праматеринской функции
Вот мы и добрались до восстановления регистров! Сместившись на одно двойное слово вправо (оно только что было вытолкнуто из стека командой RET), переходим в окно "Registers" и восстанавливаем регистры ESI, EBX, EBP, извлекая сохраненные значения из стека (листинг 3.13).:
Листинг 3.13. Содержимое регистров, ранее сохраненных в стеке вместе с адресом возврата
0012F500 77E61D49 6C2923D8 00403458
ß
6C29237D:pop esi
0012F50C 00000111 0012F540 6C2922AE ß6C29237E:pop ebx/pop ebp/ret 1Ch
Листинг 13 содержимое регистров, ранее сохраненных в стеке вместе с адресом возврата
Как вариант можно переместить регистр EIP на адрес 6C29237Dh, а регистр ESP на адрес 12F508h, после чего нажать на клавишу <F5> для продолжения выполнения программы. И этот прием действительно срабатывает! Причем, реанимированная программа уже "не ругается" на ошибку последней операции (как это было при восстановлении путем принудительного выхода из функции), а просто ее не выполняет. Красота!