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

Доступ через MSCDEX драйвер


Знаменитый MSCDEX, созданный еще во времена царствования MS-DOS, несмотря на свои многочисленныех недостатки, все-таки обеспечивал программистов всем необходимым им функционалом и достаточно полно поддерживал возможности существующих в то время приводов. Так, например, чтение отдельных секторов осуществлялось функцией 1508h прерывания INT2Fh, а если возникала необходимость спуститься на "сырой" уровень, мы всегда могли "попросить" драйвер MSCDEX передать приводу ATAPI-пакет напрямую, чем занималась функция 1510h того же прерывания (загляните в список прерываний (Interrupt List[n2k143] ), если нуждаетесь в более подробной информации).

Забавно, но возможности штатного драйвера "новейшей" и "могучей" Windows 9x, не в пример беднее и спуститься на секторный уровень, при этом, не набиве разорвав себе задницу[n2k144] , под ее управлением, по-видимому, нельзя. Судя по всему, архитекторы системы сочли секторный обмен ненужным и к тому же системно-зависимым, а "правильные" приложения должны разрабатываться как полностью переносимые и довольствующиеся исключительно стандартными вызовами интерфейса win32 API. Все остальное от лукавого!

Между тем, для сохранения обратной совместимости с программами, написанными для MS-DOS и Windows 3.1, операционная система Windows 95 поддерживает MSCDEX-интерфейс, причем по соображениям производительности, реализует его не в "настоящем" MSCDEX, который и вовсе может отсутствовать на диске, а в драйвере CD-ROM драйвере, исполняющемся в 32-разрядном защищенном режиме. Выходит, что весь необходимый нам функционал в системе все-таки есть, а значит, есть и надежда как-то до него добраться. Естественно, с уровня ядра эта задача решается без проблем, но… писать свой собственный драйвер только для того, чтобы "пробить интерфейсную шахту" к уже существующему драйверу, –— это маразм какой-то!

К счастью, готовый (и даже задокументированный!) интерфейс между win32-приложениями и MSCDEX-драйвером в системе Windows 9x действительно есть.
К несчастью, он реализован через ж…опу (и … именно через ж…)опу и не надо пытаться вычеркнуть эту фразу, иначе я шибко разозлюсь (последняя фраза предназначается в первую очередь для редакторов). В общих чертах схема "прокладывания туннеля" к драйверу MSCDEX'у выглядит приблизительно так: создав 16-разрядную динамически подключаемую библиотеку (DLL), мы получаем возможность взаимодействовать с интерфейсом DPMI через функции прерывания INT 31h.

Замечание

DPMI (DOS Protected Mode Interface) –— интерфейс, спроектированный специально для того, чтобы разработчики приложений защищенного режима, исполняющихся в среде MS-DOS, могли пользоваться функциями 16-разрядной операционной системы реального режима, коей MS-DOS и является.

Конкретно нас будет интересовать функция 1508h, –— DPMI Simulate Real Mode Interrupt, позволяющая вызывать прерывания реального режима из защищенного. Обращаясь к эмулятору MSCDEX-драйвера через родное для него прерывание INT 2Fh, мы можем делать с приводом практически все, что нам только вздумается, поскольку интерфейс драйвера MSCDEX'a,'а как уже отмечалось ранее, могуч и велик.

Таким образом, вырисовывается следующий программистский маршрут: win32 приложение à 16-разрядная DLL à DMPI Simulate RM Interrupt  à  MSCDEX  à  CDFS. Не слишком ли наворочено, а? Уж лучше воспользоваться интерфейсом ASPI (благо в Windows 95 оно[n2k145]  присутствуетесть) или засесть за написание собственного драйвера. Тем не менее, даже если вы не собираетесь управлять приводом посредством драйверачерез MSCDEX, знать о существовании такого способа взаимодействия с оборудованием все-таки небесполезно, особенно, если вы планируете заняться взломом чужих программ. В этом случае точки останова, установленные на API- функции, ничего не дадут, поскольку чтение секторов осуществляется через прерывания INT 31h (DMPI) и INT 2Fh.


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



Дополнительную информацию по этому вопросу можно найти в технической заметке Q137813, входящей в состав MSDN, распространяемой вместе с Microsoft Visual Studio и озаглавленную как "How Win32 Applications Can Read CD-ROM Sectors in Windows  95". Полный перечень DMPI- и MSCDEX-функций содержится в списке Interrupt-List'e'е Ральфа Брауна[Y146] ,[n2k147]  так что никаких проблем с использованием данного приема у вас возникнуть не должно (правда, раздобыть компилятор, способный генерировать 16-разрядный код и "линкер" (linker, иначе компановщик) под Windows 3.1 сегодня не так-то просто! К слову сказать, Microsoft Visual Studio 6.0 для этой цели уже не подходит, ибо, начиная с некоторой версии –— уже сейчас и не вспомню какой –— он утратил возможность создания проектов под операционные системы MS-DOS/Windows 3.1).

ДалееНиже приводится ключевой фрагмент программы (листинг 1.4.29), позаимствованный из MSDN, и демонстрирующий технику вызова прерываний реального режима из 16-разрядных динамически подключаемых библиотек (DLL), исполняющихся в среде Windows.

Листинг 2.1.4.3029. Ключевой фрагмент программы, демонстрирующей технику взаимодействия с драйвером MSCDEX из 16-разрядного защищенного режима

BOOL FAR PASCAL MSCDEX_ReadSector(BYTE bDrive, DWORD StartSector, LPBYTE RMlpBuffer)

{

    RMCS      callStruct;

    BOOL      fResult;

     

    // Prepare DPMI Simulate Real Mode Interrupt call structure with

    // the register values used to make the MSCDEX Absolute read call.

    // Then, call MSCDEX using DPMI and check for errors in both the DPMI

    // call and the MSCDEX call

    BuildRMCS (&callStruct);

    callStruct.eax = 0x1508;              // MSCDEX функция "ABSOLUTE READ"



    callStruct.ebx = LOWORD(RMlpBuffer);  // смещение буфера для чтения сектора

    callStruct.es  = HIWORD(RMlpBuffer);  // сегмент буфера для чтения сектора

    callStruct.ecx = bDrive;              // буква привода 0=A, 1=B, 2=C и т.д.

    callStruct.edx = 1;                   // читаем один сектор

    callStruct.esi = HIWORD(StartSector); // номер читаемого сектора(старшее слово)

    callStruct.edi = LOWORD(StartSector); // номер читаемого сектора(младшее слово)

 

    // вызываем прерывание реального режима

    if (fResult = SimulateRM_Int (0x2F, &callStruct))

          fResult = !(callStruct.wFlags & CARRY_FLAG);

 

    return fResult;

}

BOOL FAR PASCAL SimulateRM_Int(BYTE bIntNum, LPRMCS lpCallStruct)

{

    BOOL fRetVal = FALSE;      // Assume failure

 

    __asm

    {

          push di                 ; сохраняем регистр DI

          mov  ax, 0300h          ; DPMI Simulate Real Mode Interrupt

          mov  bl, bIntNum        ; номер прерывания реального режима для вызова

          mov  bh, 01h            ; бит 0 = 1; все остальные должны быть равны нулю

          xor  cx, cx             ; ничего не копируем из стека PM в стек RM

          les  di, lpCallStruct   ; указатель на структуру со значением регистров

          int  31h                ; шлюз к DMPI

          jc   END1               ; если ошибка, – прыгаем на END1

          mov  fRetVal, TRUE      ; все ОК

    END1:

          pop di                  ; восстанавливаем регистр DI

    }

 

    // возвращаемся

    return (fRetVal);

}


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