Электронная библиотека книг Александра Фролова и Григория Фролова.
Shop2You.ru Создайте свой интернет-магазин
Библиотека
Братьев
Фроловых

Операционная система MS-DOS

© Александр Фролов, Григорий Фролов
Том 1, книга 3, М.: Диалог-МИФИ, 1992.

[Назад] [Содеожание] [Дальше]

6.3. Защита программ на жестком диске

Обычно процесс установки защищенного от копирования программного пакета выглядит следующим образом:

  • в дисковод вставляется инсталляционная (установочная) дискета, с этой дискеты запускается программа установки;
  • программа установки уменьшает на единицу счетчик выполненных инсталляций (этот счетчик может находится в нестандартном секторе или в каком-нибудь другом месте на дискете);
  • если количество инсталляций, выполненных с этой дискеты, равно максимально разрешенному, на экран выдается сообщение об этом и работа программы установки завершается;
  • если ресурс инсталляций еще не исчерпан, выполняется копирование файлов устанавливаемого программного пакета на жесткий диск и другие необходимые действия;
  • выполняется настройка программного пакета на параметры используемого компьютера.

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

В этом разделе книги мы приведем несколько методов настройки программного обеспечения на конкретный компьютер:

  • привязка файлов программного пакета к их физическому расположению на диске (метод основан на том, что при восстановлении файлов на другом компьютере они будут располагаться в других секторах диска);
  • запись в неиспользуемый участок последнего кластера, распределенного файлу, контрольной информации (при выгрузке и восстановлении файлов эти неиспользуемые участки файлов пропадают);
  • привязка программы к конкретной версии BIOS, при этом используется дата трансляции BIOS и метод контрольных сумм;
  • проверка производительности отдельных подсистем компьютера.

Первый способ предполагает исследование расположения какого-либо достаточно длинного файла, записанного на диск в процессе установки на предмет определения его расположения на диске.

Программа установки, пользуясь таблицей размещения файлов FAT, определяет список кластеров, распределенных файлу и записывает этот список в конец защищаемого файла или в отдельный файл. Можно использовать, например, файл конфигурации, предназначенный для хранения текущих параметров программного пакета. Список кластеров можно зашифровать, сложив его, например, с использованием логики "ИСКЛЮЧАЮЩЕЕ ИЛИ", с каким-либо числом.

После запуска программный пакет определяет расположение защищенного файла на диске и сравнивает его с записанным при установке. Если расположение изменилось - запущена незаконная копия программного пакета.

Какие недостатки у этого способа?

Прежде всего, невозможна оптимизация диска такими программами, которые могут изменить расположение файлов на диске, например, Norton Speed Disk.

Второй недостаток связан с тем, что можно взять вторую машину с таким же типом жесткого диска, и с помощью несложной программы переписать содержимое всех секторов с диска одной машины на диск другой.

Первый недостаток можно преодолеть, используя процедуру "деинсталляции", или съема программного пакета с диска компьютера. Эта процедура заключается в том, что с жесткого диска удаляются файлы программного продукта, после чего счетчик инсталляций на дистрибутивной дискете уменьшается на единицу. После выполнения всех необходимых операций с диском можно выполнить повторную установку программного пакета.

Таким образом, можно переносить программный пакет с одной машины на другую, но нельзя его размножить на несколько компьютеров.

Второй недостаток устранить сложнее, так как при идентичности структуры дисков расположение файлов на нем будет идентично и с этим ничего нельзя сделать.

Запись контрольной информации в неиспользуемый участок файла сделает невозможным копирование программного пакета утилитами разгрузки дисков, но по-прежнему остается возможность использования программ копирования по секторам содержимого диска.

Метод проверки версии или контрольных сумм программы BIOS пригоден для защиты от копирования между компьютерами, содержащими разные версии BIOS. Однако при покупке партии компьютеров все они почти наверняка будут иметь одинаковый BIOS, поэтому, хотя этот метод достаточно прост, его эффективность относительно невысока.

Последний метод - проверка производительности отдельных подсистем компьютера - кажется нам наиболее пригодным, так как такие характеристики, как быстродействие диска или процессора являются в достаточной степени уникальными для каждого конкретного компьютера.

Мы приведем несколько примеров программ, демонстрирующих использование некоторых из перечисленных выше методов.

Сначала покажем, как получить список кластеров, распределенных файлу. Вы уже знаете, как получить этот список, пользуясь таблицей размещения файлов и дескриптором файла в каталоге. Сложность здесь заключается в том, что операционная система не предоставляет никакого документированного способа получения номера первого кластера, распределенного файлу. Вам придется последовательно просматривать дерево каталогов до тех пор, пока вы не доберетесь до вашего файла. Для просмотра дерева каталогов вам придется использовать непосредственное чтение диска и таблицу размещения файлов. Лишь найдя нужный вам каталог (содержащий файл, для которого нужно получить список кластеров) и прочитав каталог как файл в память, вы сможете воспользоваться дескриптором файла для определения номера первого кластера, распределенного файлу.

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

Мы рассмотрим более простой, но, увы, недокументированный способ получения списка кластеров, использующий таблицу открытых файлов. Эта таблица была описана в разделах книги, посвященных векторной таблице связи.

Напомним, что для каждого открытого файла эта таблица содержит, кроме всего прочего, номер первого кластера, распределенного файлу, и номер кластера файла, к которому только что выполнялось обращение - last_clu.

Идея заключается в том, чтобы открыв файл и найдя его в таблице файлов, последовательно считывать его порциями, равными по размеру одному кластеру. В процессе считывания файла поле, содержащее номер кластера last_clu будет последовательно принимать значения, равные номерам всех распределенных файлу кластеров.

Размер кластера можно получить из блока параметров BIOS BPB, который находится в загрузочной записи диска.

Приведем пример программы, которая проделывает все это и выводит на экран содержимое таблицы файлов и список кластеров для файла, путь которого передается программе в качестве параметра:

#include <dos.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <io.h>
#include "sysp.h"

void main(int argc, char *argv[]);
void show(DFCB far *);

void main(int argc, char *argv[]) {

         CVT far *cvt;
         DFT far *dft;
         unsigned i,j,k;
         DFCB far *dfcb, far *file_dfcb;
         int handle, flag, disk;

         BOOT far *boot_rec;
         int status;
         char *buf;

         char drive[_MAX_DRIVE], dir[_MAX_DIR];
         char fname[_MAX_FNAME], ext[_MAX_EXT];
         char name[12];

         printf("Информация о расположении файла\n"
                          "Copyright Frolov A. (C),1990\n");

         printf("Исследуем расположение файла '%s'\n", argv[1]);

// Открываем файл, для которого будем получать список кластеров

         handle = open(argv[1], O_BINARY);
         if(handle == 0) {
                printf("Ошибка при открытии файла!\n");
                exit(-1);
         }

// Разбиваем путь файла на составляющие компоненты:
//   - буква диска;
//   - каталог;
//   - имя файла;
//   - расширение имени

         _splitpath(argv[1], drive, dir, fname, ext);

// Комбинируем строку из имени и расширения

         strcpy(name,fname);
         for(i=0; i<8; i++) {
                if(name[i] == 0) break;
         }
         for(; i<8; i++) name[i] = ' ';
         name[8] = 0;
         strcat(name, &ext[1]);

// Преобразуем строку имени в заглавные буквы

         strupr(name);

// Вычисляем номер дисковода

         drive[0] = toupper(drive[0]);
         disk = drive[0] - 'A';

         printf("%s - %s - %s - %s : %s\n",
                drive, dir, fname, ext, name);


         cvt=get_mcvt();     // Адрес векторной таблицы связи
         dft=get_fdft(cvt);  // Адрес начала таблицы файлов

// Сбрасываем флаг поиска файла

         flag = 0;

         for(;;) {
                if(dft == (DDCB far *)0) break;  // Конец таблицы файлов

                i=dft->file_count;

                for(j=0;j<i;j++) {   // Цикл по файловым управляющим блокам

                        dfcb=(&(dft->dfcb))+j; // Адрес DFCB файла

// Ищем файл в таблице открытых файлов

                        k = memcmp(name,dfcb->filename,11);
                        if(k == 0) {

                                printf("Файл найден!\n");
                                printf("\nDFCB файла: %Fp\n\n",dfcb);

// Запоминаем адрес таблицы для найденного файла

                                file_dfcb = dfcb;

// Показываем содержимое таблицы для найденного файла

                                show(file_dfcb);
                                flag = 1;
                                break;
                        }

                 }
                 dft=get_ndft(dft);

                 if(flag == 1) break;
         }

         if(flag == 0) {
                printf("Файл не найден");
                close(handle);
                exit(-1);
         }

// Заказываем буфер для чтения BOOT-записи.
// Адрес буфера присваиваем FAR-указателю.

         boot_rec = malloc(sizeof(*boot_rec));

// Читаем загрузочную запись в буфер

         status = getboot((BOOT far*)boot_rec, disk);

// Вычисляем размер кластера в байтах

         i = boot_rec->bpb.clustsize * boot_rec->bpb.sectsize;
         printf("\nРазмер кластера, байтов : %d",i);

// Если произошла ошибка (например, неправильно указано
// обозначение диска), завершаем работу программы

         if(status) {
                printf("\nОшибка при чтении BOOT-сектора");
                close(handle);
                exit(-1);
         }

         buf = malloc(i);

         printf("\nСписок кластеров файла:\n");

// Читаем файл по кластерам, выводим номер
// последнего прочитанного кластера, который
// берем из таблицы файлов

         for(;;) {

                read(handle, buf, i);
                if(eof(handle)) break;
                printf("%d ",file_dfcb->last_clu);

         }

         close(handle);
         free(boot_rec);
         free(buf);
         exit(0);
}

// Функция для отображения содержимого таблицы файлов

void show(DFCB far *dfcb) {

        int k;

        printf("Имя файла: ");
        for(k=0;k<11;k++) {
                putchar(dfcb->filename[k]);
        }

        printf("\nКоличество file handles:       %d\n"
                "Режим доступа:                 %d\n"
                        "Поле reserv1:                  %04X\n"
                        "Информация об устройстве:      %04X\n"
                        "Адрес драйвера:                %Fp\n"
                        "Начальный кластер:             %d\n"
                        "Время:                         %04X\n"
                        "Дата:                          %04X\n"
                        "Размер файла в байтах:         %ld\n"
                        "Текущее смещение в файле:      %ld\n"
                        "Поле reserv2:                  %04X\n"
                        "Последний прочитанный кластер: %d\n"
                        "Сегмент PSP владельца файла:   %04X\n"
                        "Поле reserv7:                  %d\n"
                        "--------------------------------------\n\n",
                        dfcb->handl_num,
                        dfcb->access_mode,
                        dfcb->reserv1,
                        dfcb->dev_info,
                        dfcb->driver,
                        dfcb->first_clu,
                        dfcb->time,
                        dfcb->date,
                        dfcb->fl_size,
                        dfcb->offset,
                        dfcb->reserv2,
                        dfcb->last_clu,
                        dfcb->ownr_psp,
                        dfcb->reserv7);
}

Получив список кластеров, распределенных защищаемому файлу, вы можете зашифровать его и записать, например, в конец защищаемого файла. Впоследствии, перед началом работы, защищенная программа может проверить свое расположение на диске и сравнить его с записанным в зашифрованном списке.

Рассмотрим теперь использование BIOS для защиты от копирования программ с жесткого диска.

Для защиты используем поля, уникальные для конкретной версии BIOS. Это дата изготовления и код типа компьютера. Вся эта информация находится в BIOS, начиная с адреса F000:FFF5 и имеет следующий формат:

F000:FFF5 (8) дата изготовления BIOSBIOS;
F000:FFFC (2) не используется;
F000:FFFE (1) код типа компьютера.

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

Для удобства работы с идентификатором BIOS BIOSфайл sysp.h содержит определение типа BIOS_ID:

#pragma pack(1)


typedef struct _BIOS_ID_ {

        char date[8];
        unsigned reserve;
        char pc_type;

} BIOS_ID;
#pragma pack()

Мы подготовили функцию, которая возвращает адрес идентификатора BIOSBIOS:

/**
*.Name      getbiosi
*
*.Title     Получить адрес идентификатора BIOS
*
*.Descr     Функция возвращает адрес идентификатора BIOS,
*           имеющего следующую структуру:
*
*                  typedef struct _BIOS_ID_ {
*
*                     char date[8];
*                     unsigned reserve;
*                     char pc_type;
*
*                  } BIOS_ID;
*
*           В этой структуре:
*
*              date    - дата изготовления BIOS;
*              reserve - зарезервировано;
*              pc_type - код типа компьютера.
*
*.Params    Нет
*
*.Return    Адрес идентификатора BIOS
**/

#include <stdio.h>
#include "sysp.h"

BIOS_ID _far *getbiosi(void) {

        return((BIOS_ID _far *)(FP_MAKE(0xf000, 0xfff5)));
}

В качестве примера приведем текст программы, отображающей на экране содержимое идентификатора BIOS:

#include <stdio.h>
#include <stdlib.h>
#include "sysp.h"

void main(void);

void main(void) {

         BIOS_ID _far *id;
         int i;

         id = getbiosi();

         printf("\nИдентификатор BIOS:"
                   "\n"
                   "\nДата изготовления: ");

         for(i=0; i<8; i++) {
                putch(id->date[i]);
         }

         printf("\nРезервное поле:    %04.4X"
                   "\nТип компьютера:    %02.2X"
                   "\n",
                          id->reserve,
                          (unsigned char)id->pc_type);

         exit(0);
}
[Назад] [Содеожание] [Дальше]