Операционная система MS-DOS© Александр Фролов, Григорий ФроловТом 1, книга 3, М.: Диалог-МИФИ, 1992. 6.2. Защита дискет от копированияДискета, предназначенная для установки защищенного от копирования программного обеспечения должна быть сама защищена от копирования. Копирование дискет можно выполнить как по файлам (утилитами операционной системы COPY и XCOPY), так и по секторам (утилитой DISKCOPY, программами PCTOOLS, PCSHELL и аналогичными). Кроме того, существуют программы, специально предназначенные для копирования дискет, защищенных от копирования, например COPY2PC. Специальные программы могут копировать дискеты, содержащие только определенные защищенные программные пакеты, например, DBASE, или они могут повторять структуру дорожек диска с точностью до бита. Наиболее просто обеспечить защиту от программ копирования дискет по секторам. Можно предложить следующие достаточно простые способы, использующие нестандартное форматирование отдельных дорожек дискеты:
Очевидно, что все эти способы непригодны для защиты от таких программ копирования, которые способны копировать битовую структуру дорожек диска. Что можно порекомендовать в этом случае? Можно использовать специальную аппаратуру при записи инсталляционных дискет, которая позволяет записывать отдельные дорожки или сектора как бы с промежуточным уровнем записи. Эти участки дорожки будут читаться нестабильно, каждый раз будут получаться новые значения битов. Если такая дискета будет скопирована на обычной аппаратуре (с использованием обычных дисководов и программ побитового копирования) то все дорожки будут читаться стабильно. Если при многократном контрольном чтении указанных секторов или дорожек каждый раз будут получены разные данные - мы имеем дело с оригиналом, в противном случае - с незаконной копией. Однако дискеты с промежуточным уровнем записи все-таки могут быть скопированы с использованием специальной аппаратуры, копирующей содержимое дорожек "аналоговым" способом (как в бытовом магнитофоне). Для защиты от аналогового копирования можно использовать дискеты, на которых в некоторых местах искусственно созданы дефекты магнитного покрытия - выжженные лазером небольшие точки или просто царапины. Проверка основывается на том, что в дефектные места невозможно произвести запись информации. Если мы имеем дело с копией, то на месте дефектных секторов окажутся хорошие - копируется только информация, но не дефекты дискеты! Разумеется, что можно использовать комбинации различных методов защиты от копирования. При этом легко распознаваемые методы (нестандартный размер сектора и т.п.) можно использовать для маскировки какого-либо другого, более тонкого метода защиты. Более подробно мы остановимся на нестандартном форматировании, как на наиболее простом методе защиты от копирования, для использования которого не требуется ни специальной аппаратуры, ни специально подготовленных дискет с дефектами. Используя сведения о работе с диском на физическом уровне, приведенные в этой книге, вы сможете самостоятельно использовать метод дефектных дискет или дискет с промежуточным уровнем записи. Самое простое, что можно сделать - изменить размер секторов на дорожке. Приведем простую программу, которая форматирует двадцатую дорожку диска, создавая на ней сектора размером 256 байтов. После форматирования программа записывает в первый сектор нестандартной дорожки строку, введенную с клавиатуры. Затем для контроля содержимое этого сектора считывается и отображается на экране. Обратите внимание на изменения в таблице параметров дискеты - они необходимы для использования нестандартного размера сектора. #include <stdio.h> #include <conio.h> #include <dos.h> #include <stdlib.h> #include <bios.h> #include "sysp.h" // Номер форматируемой дорожки #define TRK 20 // Код размера сектора - 256 байт #define SEC_SIZE 1 union REGS inregs, outregs; char _far diskbuf[512]; char _far diskbuf1[512]; char buf[80]; void main(void); void main(void) { struct diskinfo_t di; unsigned status; unsigned char old_sec_size, old_fill_char, old_eot; int i, j; DPT _far *dpt_ptr; printf("\nПрограмма уничтожит содержимое" "\n20-й дорожки диска А:." "\nЖелаете продолжить? (Y,N)\n"); // Ожидаем ответ оператора и анализируем его i = getch(); if((i != 'y') && (i != 'Y')) exit(-1); // Получаем адрес таблицы параметров дискеты dpt_ptr = get_dpt(); // Сохраняем старые значения из таблицы параметров old_sec_size = dpt_ptr->sec_size; old_fill_char = dpt_ptr->fill_char; old_eot = dpt_ptr->eot; // Устанавливаем в таблице параметров дискеты // код размера сектора, символ заполнения при // форматировании, количество секторов на дорожке dpt_ptr->sec_size = SEC_SIZE; dpt_ptr->fill_char = 0x77; dpt_ptr->eot = 15; // Устанавливаем тип диска inregs.h.ah = 0x17; inregs.h.al = 3; inregs.h.dl = 0; int86(0x13, &inregs, &outregs); // Устанавливаем среду для форматирования inregs.h.ah = 0x18; inregs.h.ch = TRK; inregs.h.cl = dpt_ptr->eot; inregs.h.dl = 0; int86(0x13, &inregs, &outregs); // Подготавливаем параметры для функции форматирования di.drive = 0; di.head = 0; di.track = TRK; di.sector = 1; di.nsectors = 15; di.buffer = diskbuf; // Подготавливаем буфер формата для 15-ти секторов for(i=0, j=1; j<16; i += 4, j++) { diskbuf[i] = TRK; diskbuf[i+1] = 0; diskbuf[i+2] = j; diskbuf[i+3] = SEC_SIZE; } // Вызываем функцию форматирования дорожки status = _bios_disk(_DISK_FORMAT, &di) >> 8; printf("\nФорматирование завершилось с кодом: %d",status); // Записываем информацию в нестандартный сектор printf("\nВведите строку для записи в нестандартный сектор," "\nдлина строки не должна превышать 80 байтов" "\n->"); gets(buf); strcpy(diskbuf,buf); di.drive = 0; di.head = 0; di.track = 20; di.sector = 1; di.nsectors = 1; di.buffer = diskbuf; status = _bios_disk(_DISK_WRITE, &di) >> 8; if(status) { printf("\nОшибка при записи в нестандартный сектор: %d", status); exit(-1); } di.drive = 0; di.head = 0; di.track = 20; di.sector = 1; di.nsectors = 1; di.buffer = diskbuf1; for(i = 0; i < 3; i++) { status = _bios_disk(_DISK_READ, &di) >> 8; if( !status ) break; } printf("\nПрочитано из нестандартного сектора:\n%s\n", diskbuf1); // Восстанавливаем старые значения в // таблице параметров дискеты dpt_ptr->sec_size = old_sec_size; dpt_ptr->fill_char = old_fill_char; dpt_ptr->eot = old_eot; exit(0); } Какую информацию можно записать в нестандартный сектор? Если вы делаете инсталляционную (установочную) дискету, которая рассчитана на ограниченное количество инсталляций, нестандартный сектор - самое подходящее место для хранения счетчика инсталляций. Даже такие программы, как Norton Utilities или Norton Disk Editor, не помогут прочитать или изменить значение этого счетчика. В этот же сектор можно записать и другую информацию, необходимую для правильной инсталляции защищенного программного обеспечения. Другой пример - использование нестандартного номера дорожки. Программа форматирует дорожку (стандартным образом) с номером 81. Обычно считается, что дискеты могут содержать 40 или 80 дорожек, соответственно, с номерами 0...39 или 0...79, однако возможно использование и дорожек с большими номерами. Обычные программы копирования будут копировать только 40 или 80 дорожек, "не заметив" нашей лишней дорожки. Этим мы и воспользуемся, записав на 81-ю дорожку контрольную информацию. Для разнообразия в примере используем функции общего управления вводом/выводом GENERIC IOCTL. Первая программа предназначена для форматирования 81-й дорожки: #include <dos.h> #include <stdio.h> #include <malloc.h> #include <errno.h> #include "sysp.h" void main(void); void main(void) { union REGS reg; struct SREGS segreg; DPB _far *dbp; DPB_FORMAT _far *dbp_f; int sectors, i; printf("\nПрограмма уничтожит содержимое" "\n81-й дорожки диска А:." "\nЖелаете продолжить? (Y,N)\n"); // Ожидаем ответ оператора и анализируем его i = getch(); if((i != 'y') && (i != 'Y')) exit(-1); // Заказываем память для блока параметров устройства dbp = malloc(sizeof(DPB)); // Заказываем память для блока параметров устройства, // который будет использован для форматирования dbp_f = malloc(sizeof(DPB_FORMAT)); if(dbp == NULL || dbp_f == NULL) { printf("\nМало оперативной памяти!"); exit(-1); } // Получаем текущие параметры диска А: dbp->spec = 0; // Вызываем подфункцию 0Dh для выполнения // операции чтения текущих параметров диска А: reg.x.ax = 0x440d; reg.h.bl = 1; reg.x.cx = 0x0860; reg.x.dx = FP_OFF(dbp); segreg.ds = FP_SEG(dbp); intdosx(®, ®, &segreg); // Проверяем флаг переноса if(reg.x.cflag != 0) { printf("\nОшибка: %d",reg.x.ax); exit(-1); } // Заполняем блок парметров для форматирования. dbp->spec = 5; // Считываем из BPB количество секторов на дорожке sectors = dbp->bpb.seccnt; // Подготавливаем таблицу, описывающую формат дорожки // Записываем количество секторов на дорожке dbp->trkcnt = sectors; // Для каждого сектора на дорожке в таблицу // записываем его номер и размер. for(i = 0; i < sectors; i++) { dbp->trk[i].no = i+1; dbp->trk[i].size = 512; } // Устанавливаем новые параметры для диска А: reg.x.ax = 0x440d; reg.h.bl = 1; reg.x.cx = 0x0840; reg.x.dx = FP_OFF(dbp); segreg.ds = FP_SEG(dbp); intdosx(®, ®, &segreg); // Проверяем флаг переноса if(reg.x.cflag != 0) { printf("\nОшибка: %d",reg.x.ax); exit(-1); } // Подготавливаем блок параметров устройства, // который будет использован при вызове // операции проверки возможности форматирования // дорожки dbp_f->spec = 1; dbp_f->head = 0; dbp_f->track = 81; reg.x.ax = 0x440d; reg.h.bl = 1; reg.x.cx = 0x0842; reg.x.dx = FP_OFF(dbp_f); segreg.ds = FP_SEG(dbp_f); intdosx(®, ®, &segreg); // Проверяем флаг переноса if(reg.x.cflag != 0) { printf("\nОшибка: %d",reg.x.ax); exit(-1); } // Если указанный формат дорожки поддерживается, // поле специальных функций будет содержать 0. // Проверяем это. if(dbp_f->spec != 0) { printf("\nФормат дорожки не поддерживается!"); exit(-1); } // Заполняем блок параметров для выполнения // операции форматирования dbp_f->spec = 0; dbp_f->head = 0; dbp_f->track = 81; // Форматируем дорожку с номером 81, головка 0 reg.x.ax = 0x440d; reg.h.bl = 1; reg.x.cx = 0x0842; reg.x.dx = FP_OFF(dbp_f); segreg.ds = FP_SEG(dbp_f); intdosx(®, ®, &segreg); // Проверяем флаг переноса if(reg.x.cflag != 0) { printf("\nОшибка: %d",reg.x.ax); exit(-1); } // Освобождаем буфера free(dbp); free(dbp_f); exit(0); } Для записи и последующего чтения информации на дополнительную дорожку можно использовать следующую программу: #include <dos.h> #include <stdio.h> #include <malloc.h> #include <errno.h> #include "sysp.h" void main(void); void main(void) { union REGS reg; struct SREGS segreg; DPB_WR _far *dbp_wr; char buf[1000]; char buf1[80]; int sectors, i; // Заказываем память для блока параметров устройства, // который будет использован для чтения/записи dbp_wr = malloc(sizeof(DPB_WR)); if(dbp_wr == NULL) { printf("\nМало оперативной памяти!"); exit(-1); } // Записываем информацию в нестандартный сектор printf("\nВведите строку для записи в нестандартный сектор," "\nдлина строки не должна превышать 80 байтов" "\n->"); gets(buf1); strcpy(buf,buf1); // Заполняем блок параметров для выполнения // операции записи. dbp_wr->spec = 0; dbp_wr->head = 0; dbp_wr->track = 81; dbp_wr->sector = 0; dbp_wr->sectcnt = 1; dbp_wr->buffer = buf; // Выполняем операцию записи reg.x.ax = 0x440d; reg.h.bl = 1; reg.x.cx = 0x0841; reg.x.dx = FP_OFF(dbp_wr); segreg.ds = FP_SEG(dbp_wr); intdosx(®, ®, &segreg); // Проверяем флаг переноса if(reg.x.cflag != 0) { printf("\nОшибка при записи: %d",reg.x.ax); exit(-1); } // Заполняем блок параметров для выполнения // операции чтения. dbp_wr->spec = 0; dbp_wr->head = 0; dbp_wr->track = 81; dbp_wr->sector = 0; dbp_wr->sectcnt = 1; dbp_wr->buffer = buf; // Выполняем операцию чтения дорожки reg.x.ax = 0x440d; reg.h.bl = 1; reg.x.cx = 0x0861; reg.x.dx = FP_OFF(dbp_wr); segreg.ds = FP_SEG(dbp_wr); intdosx(®, ®, &segreg); // Проверяем флаг переноса if(reg.x.cflag != 0) { printf("\nОшибка при чтении: %d",reg.x.ax); exit(-1); } printf("\nПрочитано из нестандартного сектора:\n%s\n", buf); // Освобождаем буфер free(dbp_wr); exit(0); } Более интересный способ защиты дискет от копирования связан с использованием при форматировании нестандартного чередования секторов на дорожке. В приведенной ниже программе использовано "обратное" расположение секторов - вначале идет сектор с номером 15, затем 14 и т.д. #include <stdio.h> #include <conio.h> #include <dos.h> #include <stdlib.h> #include <bios.h> #include "sysp.h" // Номер форматируемой дорожки #define TRK 20 // Код размера сектора - 512 байт #define SEC_SIZE 2 union REGS inregs, outregs; char _far diskbuf[512]; void main(void); void main(void) { struct diskinfo_t di; unsigned status; unsigned char old_sec_size, old_fill_char, old_eot; int i, j; DPT _far *dpt_ptr; printf("\nПрограмма уничтожит содержимое" "\n20-й дорожки диска А:." "\nЖелаете продолжить? (Y,N)\n"); // Ожидаем ответ оператора и анализируем его i = getch(); if((i != 'y') && (i != 'Y')) exit(-1); // Получаем адрес таблицы параметров дискеты dpt_ptr = get_dpt(); // Сохраняем старые значения из таблицы параметров old_sec_size = dpt_ptr->sec_size; old_fill_char = dpt_ptr->fill_char; old_eot = dpt_ptr->eot; // Устанавливаем в таблице параметров дискеты // код размера сектора, символ заполнения при // форматировании, количество секторов на дорожке dpt_ptr->sec_size = SEC_SIZE; dpt_ptr->fill_char = 0xf6; dpt_ptr->eot = 15; // Устанавливаем тип диска inregs.h.ah = 0x17; inregs.h.al = 3; inregs.h.dl = 0; int86(0x13, &inregs, &outregs); // Устанавливаем среду для форматирования inregs.h.ah = 0x18; inregs.h.ch = TRK; inregs.h.cl = dpt_ptr->eot; inregs.h.dl = 0; int86(0x13, &inregs, &outregs); // Подготавливаем параметры для функции форматирования di.drive = 0; di.head = 0; di.track = TRK; di.sector = 1; di.nsectors = 15; di.buffer = diskbuf; // Подготавливаем буфер формата для 15-ти секторов // Используем обратный порядок расположения секторов // на дорожке for(i=0, j=15; j>0; i += 4, j--) { diskbuf[i] = TRK; diskbuf[i+1] = 0; diskbuf[i+2] = j; diskbuf[i+3] = SEC_SIZE; } // Вызываем функцию форматирования дорожки status = _bios_disk(_DISK_FORMAT, &di) >> 8; printf("\nФорматирование завершилось с кодом: %d",status); // Восстанавливаем старые значения в // таблице параметров дискеты dpt_ptr->sec_size = old_sec_size; dpt_ptr->fill_char = old_fill_char; dpt_ptr->eot = old_eot; exit(0); } Для анализа используемого чередования секторов можно использовать следующую программу, которая пытается прочитать подряд два расположенных рядом сектора с номерами 1 и 2. Если сектора на дорожке чередуются обычным образом, то сектора с номерами 1 и 2 находятся рядом. Если же дорожка отформатирована приведенной выше программой, то эти сектора находятся на максимальном удалении друг от друга. Программа анализирует время, необходимое на то, чтобы 50 раз подряд прочитать эти два сектора на двадцатой дорожке. Вначале используется головка 0 - это нестандартная дорожка, затем - головка 1, для которой раньше было выполнено стандартное форматирование. На экран выводятся не только времена, но и их отношение, которое и может служить критерием при определении того, с чем мы имеем дело - с оригинальной дискетой или с ее копией. #include <stdio.h> #include <conio.h> #include <bios.h> #include <dos.h> #include <stdlib.h> #include <time.h> char _far diskbuf[1024]; void main(void); void main(void) { unsigned status = 0, i, j; struct diskinfo_t di; time_t start, end; float t1, t2; // Читаем первый сектор дорожки для синхронизации таймера di.drive = 0; di.head = 0; di.track = 20; di.sector = 1; di.nsectors = 1; di.buffer = diskbuf; for(i = 0; i < 3; i++) { status = _bios_disk(_DISK_READ, &di) >> 8; if( !status ) break; } // Отсчет времени начинаем сразу после чтения сектора, // это позволит компенсировать время, необходимое на разгон // мотора дисковода. start = clock(); // Повторяем 50 раз чтение секторов с номерами 1 и 2 for(j=0; j<50; j++) { di.drive = 0; di.head = 0; di.track = 20; di.sector = 1; di.nsectors = 2; di.buffer = diskbuf; for(i = 0; i < 3; i++) { status = _bios_disk(_DISK_READ, &di) >> 8; if( !status ) break; } } end = clock(); t1 = ((float)end - start) / CLK_TCK; printf("Время для головки 0: %5.1f\n",t1); // Выполняем аналогичную процедуру для дорожки, // которая была отформатирована обычным способом. di.drive = 0; di.head = 1; di.track = 20; di.sector = 1; di.nsectors = 1; di.buffer = diskbuf; for(i = 0; i < 3; i++) { status = _bios_disk(_DISK_READ, &di) >> 8; if( !status ) break; } start = clock(); for(j=0; j<50; j++) { di.drive = 0; di.head = 1; di.track = 20; di.sector = 1; di.nsectors = 2; di.buffer = diskbuf; for(i = 0; i < 3; i++) { status = _bios_disk(_DISK_READ, &di) >> 8; if( !status ) break; } } end = clock(); t2 = ((float)end - start) / CLK_TCK; printf("Время для головки 0: %5.1f\n",t2); printf("\nОтношение времен чтения для головок 0 и 1: %5.1f", t1/t2); } Приведенный выше метод защиты дискет от копирования проверен на программе COPY2PC. Оказалось, что эта программа не копирует чередование секторов на дорожке. |