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

Доступ посредствомчерез драйвера CD-ROM-драйвер


Управление драйверами устройств в операционных системах семейства Windows осуществляется посредством вызова функции DeviceIoControl, отвечающей за посылку специальных FSCTL/IOCTL- – команд. Префикс FS-

свидетельствует о принадлежности данной команды к файловой системе и в контексте настоящей публикации не представляет для нас никакого интереса. Команды с префиксом IO- относятся к устройству ввода/вывода, а точнее –— к его драйверу.

Функция DeviceIoControl просто передает такую команду, как она есть, совершенно не задумываясь о ее "физическом смысле". Следовательно, совершенно бессмысленно искать перечень доступных IOCTL –- команд в описании функции DeviceIoControl. Их там нет! Точнее, здесь приводятся лишь стандартные IOCTL-команды, а вся остальная информация по этому вопросу содержится в DDK (Device Driver Kit).

Там, в частности, мы найдем, что для чтения оглавления диска используется команда IOCTL_CDROM_READ_TOC, а для перечисления адресов сессий многосессионных дисков –— IOCTL_CDROM_GET_LAST_SESSION. Также обратите свое внимание на команду IOCTL_CDROM_READ_Q_CHANNEL, обеспечивающую извлечение информации из Q-?канала подкода (для извлечения ключевых меток –— это актуально).

Чтение "сырых" секторов осуществляется командой IOCTL_CDROM_RAW_READ, возможности которой, к сожалению, ограничены только лишь дисками CD-DA (Compact Disk Digital Audio)-дисками только. Посекторное чтение с дисков CD-DATA-дисков ни на сыром, ни на "сухом" уровнях не поддерживается. В соответствии с принятой политикой безопасности, никакое приложение не должно действовать в обход системы безопасности, в противном случае, злоумышленник сможет без труда дорватьсядобраться до конфиденциальных данных, просто прочитав диск на секторном уровне. Штатные драйверадрайвераы, которыми укомплектованы операционные системы семейства Windows, всецело следуют этим требованиям, хотя сторонние разработчики могут, при желании, и нарушить этот запрет.
В состав NT DDK  входит исходный текст демонстрационного драйвера CD-ROM-драйвера ("NTDDK\src\storage\class\cdrom\"), который после небольшой обработки "напильником", согласитьсясогласится читать диски всех типов, не задавая при этом глупых вопросов. Найдите в теле файла cdrom.c следующую строку "if (rawReadInfo->TrackMode == CDDA) {" и перейдите к ветке, чей код операции (OperationCode[Y100] ) равен SCSIOP_READ. А теперь модифицируйте код так, чтобы она (эта ветка) получала управление и во всех остальных случаях.

Замечание

Функция Команда IRP_MJ_READ, присутствующая в DDK, и по идее обеспечивающая возможность чтения отдельных логических блоков, является внутренней функцией драйвера и доступ к последней с прикладного уровня закрыт; пытаться использовать ее в паре с функцией DeviceIoControl — бессмысленно.

В таблице 1.4.1 приведено описание IOCTL-команд штатного драйвера CD-ROM (за более подробной информацией обращайтесь к DDK).

Таблица 21.4.11. Описание IOCTL-команд штатного драйвера CD-ROM драйвера (за более подробной информацией обращайтесь к DDK)



IOCTL-команда

Описание

IOCTL_CDROM_CHECK_VERIFY,

IOCTL_STORAGE_CHECK_VERIFY (0x24800h)

Определяет факт смены диска (открытия/закрытия лотка)

IOCTL_CDROM_CLOSE_DOOR*

IOCTL_STORAGE_LOAD_MEDIA (0x2D480Ch)

Закрывает лоток привода

IOCTL_CDROM_FIND_NEW_DEVICES,

IOCTL_STORAGE_FIND_NEW_DEVICES (0x24818h)

Перечисляет новые приводы, подключенные после загрузки системы или последнего вызова данной команды

IOCTL_CDROM_GET_CONTROL

Сообщает текущую позицию воспроизведения аудио

IOCTL_CDROM_GET_DRIVE_GEOMETRY (0x2404Ch)

Определяет тип лазерного диска и его геометрию (количест-во секторов на диске, размер одного сектора и т. д.)

IOCTL_CDROM_GET_LAST_SESSION (0x24038h)

Перечисляет стартовые адреса сессий и записывает их в буфер TOC, читаемый командой IOCTL_CDROM_READ_TOC

IOCTL_CDROM_GET_VOLUME (0x24014h)

Возвращает текущий уровень громкости с CD-ROM

IOCTL_CDROM_PAUSE_AUDIO (0x2400Ch)

Временно останавливает воспроизведение аудио

IOCTL_CDROM_PLAY_AUDIO_MSF (0x24018h)

Инициирует процесс воспроизведения аудио от сих до сих

IOCTL_CDROM_RAW_READ (0x2403Eh)

Выполняет "Сыроесырое" чтение секторов с аудиодисков

IOCTL_CDROM_READ_Q_CHANNEL (0x2402Ch)

Читаеттение данныех Q-канала подкода

IOCTL_CDROM_READ_TOC (0x24000h)

Читает оглавление диска

IOCTL_CDROM_RESUME_AUDIO (0x24010h)

Продолжаетить воспроизведение аудио

IOCTL_CDROM_SEEK_AUDIO_MSF (0x24004h)

Позиционирует оптическую головку

IOCTL_CDROM_SET_VOLUME (0x24028h)

Устанавливаетовить уровень громкости с CD-ROM

IOCTL_CDROM_STOP_AUDIO (0x24008h)

Останавливает воспроизведение аудио

<


* — устарело и ныне удалено из DDK

Функции DeviceIoControl  всегда предшествует вызов функции CreateFile, возвращающей дескриптор соответствующего устройства, задаваемого в виде "\\.\X:", где XX –— буквенное обозначение того привода, с которым мы собрались работать, причем флаг dwCreationDisposition должен быть установлен в состояние OPEN_EXISTING, иначе вы потерпите неудачу. Типовой пример вызова функции приведен далее в листингах 2.1.4.1—2.1.4.2.ниже

Замечание

Windows NT регистрирует устройство с именем \\.\CdRomx, где x —– номер привода, считая от нуля, которое ссылается на тот же самый драйвер, что и буквенное обозначение диска и обладает тем же самым набором функций.):

Листинг 2.1.4.1. Пример открытия устройства

HANDLE hCD;                                             // дескриптор привода

hCD=CreateFile("\\\\.\\X:", GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, 0,0);

if (hCD == INVALID_HANDLE_VALUE)                        // ошибка

Прототип самой же функции DeviceIoControl выглядит так, как это показано в листинге 2.1.4.2.:

Листинг 2.1.4.2. Прототип функции DeviceIoControl

BOOL DeviceIoControl(

  HANDLE hDevice,               // дескриптор устройства

  DWORD dwIoControlCode,        // IOCTL-код команды для выполнения

  LPVOID lpInBuffer,            // указатель на входной буфер

                                // (Irp->AssociatedIrp.SystemBuffer)

  DWORD nInBufferSize,          // размер входного буфера в байтах

  LPVOID lpOutBuffer,           // указатель на выходной буфер

                                // (Irp->AssociatedIrp.SystemBuffer)

  DWORD nOutBufferSize,         // размер выходного буфера в байтах

  LPDWORD lpBytesReturned,      // указатель на счетчик кол-ва возвращенных байт

  LPOVERLAPPED lpOverlapped     // указатель на структуру для асинхронных операций

);

Здесь:

q      hDevice —– тот самый дескриптор, который был только что возращен функцией CreateFile;



q      dwIoControlCode —– IOCTL-код нашей операции;

q      lpInBuffer —– указатель на буфер, содержащий данные, подготовленные для передачи устройству (как правило, аргументы команды). В процессе выполнения функции содержимое буфера копируется в Irp?>AssociatedIrp.SystemBuffer. Это на тот случай, чтобы, увидев такую абракадабру в DDK, вы не хватались за сердце и не пытались "скормить" функции DeviceIoControl всю IRP-структуру;

q      nInBufferSize —– размер входного буфера в байтах. В процессе выполнения функции он копируется в структуру Parameters.DeviceIoControl.InputBufferLength;

q      lpOutBuffer —– указатель на выходной буфер, в который помещается содержимое Irp?>AssociatedIrp.SystemBuffer;

q      nOutBuffersSize —– указатель на двойное слово, в которое будет записано количест-во байт, возвращенных драйвером через выходной буфер.

Если операция завершилась успешно, функция возвращает ненулевое значение, и нуль —– в противном случае. За более подробной информацией об ошибке возвращайтесь к функции GetLastError.

Передача IOCTL-команд устройству не требует наличия прав администратора (за тем исключением, когда устройство открывается с флагом GENETIC_WRITE), что значительно увеличивает "эргономичность" защитных механизмов, базирующихся на ее основе (Кстати, о защитных механизмах, точнее их стойкости ко взлому. Поскольку функция DeviceIoControl к числу популярных явно не относится, она демаскирует "штаб-квартиру" защитного механизма, и его становится очень легко "запеленговать". Достаточно поставить на функцию DeviceIoControl точку останова и дождаться, пока передаваемая ей IOCTL-команда не примет одно из вышеперечисленных значений. На функцию CreateFile точку останова лучше не ставить, –— т. к. это даст множество ложных срабатываний (функция CreateFile вызывается всякий раз при открытии/создании какого- либо файла).


А вот попробовать поискать в теле программы текстовую строку ""\\.\"" все-таки стоит. И, если она действительно будет найдена, вам останется лишь подбежать курсором к перекрестной ссылке и нажатьдолбануть на клавишупо <Enter>'у. Все! Защитный код перед вами!)

Для лучшего понимания данного способа взаимодействия между прикладной программой и драйвером далее в листинге 1.4.3ниже приведен ключевой фрагмент функции, как раз и осуществляющей такое взаимодействие (обработка ошибок по соображениям наглядности опущена).:

Листинг 2.1.4.3. [/IOCTL.CDDA.raw.read.c] Функция, демонстрирующая технику чтения "сырых" секторов через CDFS-драйвер (только для дисков CD-DA дисков!)

//--[ReadCDDA]-----------------------------------------------------------------

//

//                  читает сектор в "сыром" виде с CDDA-дисков

//                  ==========================================

// ARG:

//      drive            - имя устройства, с которого читать (например "\\\\.\\X:")

//      start_sector     - номер первого читаемого сектора

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

//     

// RET:

//      == 0             - ошибка

//      != 0             - указатель на буфер, содержащий считанные сектора

//

// NOTE:

//      функция поддерживает только диски тех типов, что поддерживает драйвер

//      CDFS, который она и использует, а штатный драйвер Windows NT поддерживает

//      лишь CDDA-диски

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

char* ReadCDDA(char *drive, int start_sector, int n_sec)

{

      // поддерживаемые типы треков

      typedef enum _TRACK_MODE_TYPE {

            YellowMode2,                     // native MODE 2 (не CD-data)

            XAForm2,                         // XA MODE 2 Form 2 (VideoCD)

            CDDA                             // Audio-CD

      } TRACK_MODE_TYPE, *PTRACK_MODE_TYPE;

     

      // аргумент IOCTL-команды IOCTL_RAW_READ



      typedef struct __RAW_READ_INFO {

            LARGE_INTEGER      DiskOffset;    // смещение в байтах лог. блоков

            ULONG              SectorCount;   // кол-во секторов для чтения

            TRACK_MODE_TYPE    TrackMode;     // режим читаемого трека

      } RAW_READ_INFO, *PRAW_READ_INFO;

     

      #define CDROM_RAW_SECTOR_SIZE        2352

      #define CDROM_SECTOR_SIZE            2048

     

      int      a;

      HANDLE   hCD;

      DWORD    x_size;

      char     *szDrive;

      BOOL     fResult = 0;

      unsigned char      *buf;

      RAW_READ_INFO      rawRead;

     

      // ПОДГОТАВЛИВАЕМ СТРУКТУРУ RAW_READ_INFO, передаваемую драйверу CD-ROM'а

      rawRead.TrackMode             = CDDA;          // тип диска – Audio CD

      rawRead.SectorCount           = n_sec;           // кол-во читаемых секторов

      rawRead.DiskOffset.QuadPart   = start_sector * CDROM_SECTOR_SIZE;

      //                                             ^^^^^^^^^^^^^^^^^^

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

      // а номером своего первого байта. сквозная нумерация байтов от

      // первого до последнего байта диска теоритически обеспечивает

      // полное абстрагирование от конкретного оборудования

      // (размер одного сектора возвращается IOCTL-командой

      // IOCTRL_CDROM_GET_DRIVE_GEOMETRY), но практически архитекторами

      // драйвера допущен грубый ляп, "благодаря" которому драйвер

      // принимает вовсе не сквозные номера байт,

      // а start_address * CDROM_SECTOR_SIZE, где

      // SECTOR_SIZE – размер логического блока, который в данном случае равен

      // стандартному размеру сектора CDDATA-диска (2048 байт для справки),

      // в то время как размер сектора CDDA-дисков составляет 2352 байта

      // поэтому DiskOffset равен start_secor * CDROM_SECTOR_SIZE, а размер

      // буфера должен быть равен start_secor * CDROM_RAW_SECTOR_SIZE

     

      // ВЫДЕЛЯЕМ ПАМЯТЬ



      buf = malloc(CDROM_RAW_SECTOR_SIZE * n_sec);

     

      // ПОЛУЧАЕМ ДЕСКРИПТОР УСТРОЙСТВА

      hCD = CreateFile(drive,GENERIC_READ,FILE_SHARE_READ,0,OPEN_EXISTING,0,0);

      if (hCD != INVALID_HANDLE_VALUE)

     

      // ПЕРЕДАЕМ ДРАЙВЕРУ ПРИВОДА КОМАНДУ IOCTL_CDROM_RAW_READ

      fResult = DeviceIoControl(      hCD, 0x2403E /* IOCTL_CDROM_RAW_READ */,

                                      &rawRead, sizeof(RAW_READ_INFO),

                                      buf, CDROM_RAW_SECTOR_SIZE*n_sec,

                                      &x_size, (LPOVERLAPPED) NULL);

     

      // ВЫВОДИМ РЕЗУЛЬТАТ (если есть, что выводить)

      if (fResult)

           for (a = 0; a <= x_size; ++a) printf("%02X%s",buf[a],(a%24)?" ":"\n");

      else

           printf("-ERROR"); printf("\n");

     

      // СВАЛИВАЕМ

      CloseHandle(hCD); return (fResult)?buf:0;

}

Еще один демонстрационный пример приведен в листинге 1.4.4, изучение которого бывает полезно при анализе некоторых защищенных дисковниже. Он иллюстрирует технику чтения TOC (Table Of Content) —– своеобразный аналог таблицы разделов лазерных аудиодисков.

Листинг 2.1.4.4. [/IOCTL.read.TOC.c] еще один Пример программы, взаимодействующей с CDFS-драйвером через IOCTL и читающей содержимое TOC'а (с расшифровкой), изучение которого бывает полезно при анализе некоторых защищенных дисков

/*--------------------------------------------------------------------------

 *

 *                        ЧТЕНИЕ И РАСШИФРОВКА TOC

 *                        ========================

 *

 * build 0x001 @ 26.05.2003

--------------------------------------------------------------------------*/

main(int argc, char **argv)

{

int              a;

HANDLE           hCD;

unsigned char    *buf;

WORD             TOC_SIZE;

BYTE             n_track;

DWORD            x_size,b;

     

#define DEF_X      "\\\\.\\G:"                  // привод по умолчанию



#define argCD      ((argc>1)?argv[1]:DEF_X)

     

// ПРОВЕРКА АРГУМЕНТОВ

if (argc < 2) {fprintf(stderr, "USAGE: IOCTL.read.TOC \\\\.\\X:\n"); return 0;}

     

// TITLE

fprintf(stderr," simple TOC reader via IOCTL\n");

     

// ВЫДЕЛЯЕМ ПАМЯТЬ

buf = (char *) malloc(buf_len);

      

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

hCD=CreateFile(argv[1], GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0);

     

// ВЫХОДИМ, ЕСЛИ ОШИБКА

if (hCD == INVALID_HANDLE_VALUE)

      {fprintf(stderr,"-ERR: %x\n", GetLastError()); return 0;}

     

// ПЕРЕДАЕМ ДРАЙВЕРУ КОМАНДУ CDROM_READ_TOC

if (DeviceIoControl(      hCD, 0x24000 /* IOCTL_READ_TOC */,

                          0, 0, buf, buf_len, &x_size, 0) != 0)

   {

            // ПОЛУЧАЕМ ДЛИНУ ТОС'а (она записана в обратном порядке)

            TOC_SIZE = buf[0]*0x100L + buf[1];

            printf("TOC Data Length........%d\n",TOC_SIZE);

           

            // декодируем остальную информацию

            printf("First Session Number...%d\n",buf[2]);

            printf("Last Session Number....%d\n\n",(n_track=buf[3]));

            for (a = 1; a <= n_track; a++)

            {

                  printf("track %d\n{\n",a);

                  printf("\treserved.............%x\n",buf[a * 8 - 4]);

                  printf("\tADR|control..........%d\n",buf[a * 8 - 3]);

                  printf("\ttrack number.........%d\n",buf[a * 8 - 2]);

                  printf("\treserved.............%d\n",buf[a * 8 - 1]);

                  printf("\treserved.............%d\n",buf[a * 8 + 0]);

                  printf("\tmin..................%d\n",buf[a * 8 + 1]);

                  printf("\tsec..................%d\n",buf[a * 8 + 2]);

                  printf("\tframe................%d\n",buf[a * 8 + 3]);

                  printf("}\n\n");

            }

           

            // выводим содержимое TOC'a в "сыром" виде

            printf("\n\t\t\t* * * RAW * * *\n");

            for(a = 0; a < x_size; a++)

                  printf("%02X%s",(unsigned char)buf[a],((a+1)%22)?" ":"\n");

            printf("\n\t\t\t* * *  *  * * *\n");

      }

}


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