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

MS-DOS для программиста

© Александр Фролов, Григорий Фролов
Том 19, М.: Диалог-МИФИ, 1995, 253 стр.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Список кластеров, распределенных файлу

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

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

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

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

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

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

Программа CLUSTLST

Приведем исходный текст программы CLUSTLST (листинг 5.6). Эта программа выводит на экран содержимое таблицы файлов и список кластеров для файла, полный путь которого передается программе в качестве параметра.


Листинг 5.6. Файл clustlst\clustlst.cpp


#include <dos.h>
#include <bios.h>
#include <alloc.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <io.h>
#include <ctype.h>

typedef struct _DFCB_
{
  unsigned handl_num;
  unsigned char access_mode;
  unsigned reserv1;
  unsigned dev_info;
  void far *driver;
  unsigned first_clu;
  unsigned time;
  unsigned date;
  unsigned long fl_size;
  unsigned long offset;
  unsigned reserv2;
  unsigned reserv7;
  unsigned reserv3;
  char reserv4;
  char filename[11];
  char reserv5[6];
  unsigned ownr_psp;
  unsigned reserv6;
  unsigned last_clu;
  char reserv8[4];
} DFCB;
typedef DFCB far* LPDFCB;

typedef struct _DFT_
{
  struct _DFT_ far *next;
  unsigned file_count;
  DFCB dfcb;
} SFT;
typedef SFT far* LPSFT;

typedef struct
{
  unsigned mcb_seg;
  void far *dev_cb;
  void far *file_tab;
  void far *clock_dr;
  void far *con_dr;
  unsigned max_btbl;
  void far *disk_buf;
  void far *drv_info;
  void far *fcb_tabl;
  unsigned fcb_size;
  unsigned char num_bdev;
  unsigned char lastdriv;
} CVT;
typedef CVT far* LPCVT;

typedef struct _EBPB_
{
  unsigned sectsize;
  char clustsize;
  unsigned ressecs;
  char fatcnt;
  unsigned rootsize;
  unsigned totsecs;
  char media;
  unsigned fatsize;
  unsigned seccnt;
  unsigned headcnt;
  unsigned hiddensec_low;
  unsigned hiddensec_hi;
  unsigned long drvsecs;
} EBPB;

typedef struct _BOOT_
{
  char jmp[3];
  char oem[8];
  EBPB bpb;
  char drive;
  char reserved;
  char signature;
  unsigned volser_lo;
  unsigned volser_hi;
  char label[11];
  char fat_format[8];
  char boot_code[450];
} BOOT;

LPSFT get_fsft(LPCVT cvt);
LPSFT get_nsft(LPSFT sft);
void show(DFCB far *);
int getboot(BOOT far *boot, int drive);

union REGS   regs;
struct SREGS sregs;

int main(int argc, char *argv[])
{
  CVT far *cvt;
  SFT far *sft;
  unsigned i,j,k;
  DFCB far *dfcb, far *file_dfcb;
  int handle, flag, disk;

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

  char drive[128], dir[128];
  char fname[20], ext[10];
  char name[12];

  printf("Информация об открытых файлах DOS, "
    "Frolov A., (C) 1995\n");

  // Открываем файл, для которого будем
  // получать список кластеров
  handle = open(argv[1], O_BINARY );
  if(handle == 0)
  {
    printf("Ошибка при открытии файла\n");
    return(-1);
  }

  // Разбиваем путь к файлу на компоненты:
  //   - диск;
  //   - каталог;
  //   - имя файла;
  //   - расширение имени
  _splitpath(argv[1], drive, dir, fname, ext);

  if(drive[0] == '\0' || dir[0] == '\0' || argc < 2)
  {
    printf("\nУкажите полный путь к файлу\n");
    return(-1);
  }

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

  // Комбинируем строку из имени и расширения
  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]);
  for(i = 8; i < 12; i++)
  {
    if(name[i] == 0) break;
  }
  for(; i < 12; i++) name[i] = ' ';
  name[12] = 0;

  // Преобразуем строку имени в заглавные буквы
  strupr(name);

  // Вычисляем номер диска
  drive[0] = toupper(drive[0]);
  disk = drive[0] - 'A';

  // Получаем адрес векторной таблицы связи
  regs.h.ah = 0x52;
  intdosx(&regs, &regs, &sregs);

  // Передвигаем указатель на поле msb_seg
  cvt = (LPCVT)MK_FP(sregs.es, regs.x.bx - 2);

  // Адрес начала таблицы файлов
  sft = get_fsft(cvt);

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

  for(;;)
  {
    // Конец таблицы файлов
    if(sft == (SFT far *)NULL) break;
    i = sft->file_count;

    for(j=0;j<i;j++)
    {
      dfcb = (&(sft->dfcb)) + j;

      // Ищем файл в таблице открытых файлов
      k = _fmemcmp((const void far*)name,
        (const void far*)dfcb->filename, 11);
      if(k == 0)
      {
	printf("\nDFCB файла:                   "
               " %Fp", dfcb);

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

        // Показываем содержимое таблицы
        show(file_dfcb);
	flag = 1;
	break;
      }
    }
    if(flag == 1) break;

    sft = get_nsft(sft);
  }

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

  // Заказываем буфер для чтения загрузочной записи
  boot_rec = (BOOT far*)farmalloc(sizeof(*boot_rec));
  if(boot_rec == NULL)
  {
    printf("Мало памяти");
    close (handle);
    return(-1);
  }

  // Читаем загрузочную запись в буфер
  status = getboot((BOOT far*)boot_rec, disk);

  // Вычисляем размер кластера в байтах
  i = boot_rec->bpb.clustsize * boot_rec->bpb.sectsize;
  printf("Размер кластера, байт :        %d",i);

  // Если произошла ошибка (например, неправильно указано
  // обозначение диска), завершаем работу программы
  if(status)
  {
    printf("\nОшибка при чтении загрузочного сектора");
    close (handle);
    return(-1);
  }

  buf = (char*)malloc(i);
  if(buf == NULL)
  {
    printf("Мало памяти");
    close (handle);
    return(-1);
  }

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

  // Читаем файл по кластерам, выводим номер
  // последнего прочитанного кластера, который
  // берем из таблицы файлов
  for(;;)
  {
    read (handle, buf, i);
    if(eof(handle)) break;
    printf("%u ",file_dfcb->last_clu);
  }

  close (handle);
  farfree(boot_rec);
  free(buf);
  return(0);
}

// Функция для отображения содержимого таблицы файлов
void show(DFCB far *dfcb)
{
  int k;

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

  printf("\nКоличество идентификаторов:    %d\n"
    "Режим доступа:                 %d\n"
    "Поле reserv1:                  %04X\n"
    "Информация об устройстве:      %04X\n"
    "Адрес драйвера:                %Fp\n"
    "Начальный кластер:             %u\n"
    "Время:                         %04X\n"
    "Дата:                          %04X\n"
    "Размер файла в байтах:         %ld\n"
    "Текущее смещение в файле:      %ld\n"
    "Поле reserv2:                  %04X\n"
    "Последний прочитанный кластер: %u\n"
    "Сегмент PSP владельца файла:   %04X\n"
    "Поле reserv7:                  %u\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);
}

LPSFT get_nsft(LPSFT sft)
{
  LPSFT sft_next;

  sft_next = sft->next;
  if(FP_OFF(sft_next) == 0xffff)
    return((LPSFT)NULL);

  return(sft_next);
}

LPSFT get_fsft(LPCVT cvt)
{
  LPSFT sft;
  sft = (LPSFT)cvt->file_tab;
  return(sft);
}

int getboot(BOOT far *boot, int drive)
{
  struct
  {
    unsigned long first_sect;
    unsigned nsect;
    void far* buf;
  } cb;

  cb.first_sect = 0;
  cb.nsect = 1;
  cb.buf = (void far*)boot;

  _BX  = FP_OFF(&cb);
  _DS = FP_SEG(&cb);
  _CX  = 0xffff;
  _DX  = 0;
  _AX  = drive;
  asm int 25h
  asm pop ax
  asm jc err

  return 0;
err:
  return 1;
}

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

Привязка к BIOS

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

Программа может определить дату изготовления BIOS, прочитав 8 байт из области памяти, расположенной по адресу F000h:FFF5h.

Более подробную информацию о BIOS можно получить, воспользовавшись функцией C0h прерывания INT 15h . Эта функция возвращает в регистрах ES:BX адрес таблицы конфигурации:

Смещение, байт Размер, байт Описание
0 2 Размер таблицы в байтах
2 1 Код модели компьютера
3 1 Дополнительный код модели
4 1 Версия изменений BIOS (0 - первая реализация, 2 - вторая и т. д.)
5 1 Байт конфигурации оборудования
6 2 Зарезервировано
8 2 Зарезервировано

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

Бит Описание
0 Зарезервировано
1 Если этот бит установлен, компьютер оборудован шиной Micro Channel, в противном случае используется шина ISA, PCI или EISA
2 Используется расширенная область данных BIOS
3 BIOS способна ожидать внешние события
4 Каждый раз после вызова прерывания от клавиатуры INT 9h вызывается функция 4Fh прерывания INT 15h
5 В компьютере есть часы реального времени
6 Имеется второй контроллер прерываний
7 Для работы с диском BIOS использует канал 3 контроллера прямого доступа к памяти

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

Программа BIOSVER

В листинге 5.7 приведен исходный текст программы BIOSVER, которая отображает дату изготовления BIOS, а также расширенную информацию о BIOS, полученную с помощью функции C0h прерывания INT 15h .


Листинг 5.7. Файл biosver\biosver.cpp


#include <stdio.h>
#include <conio.h>
#include <dos.h>

typedef struct _BIOSINFO_
{
  unsigned size;
  unsigned char model;
  unsigned char submodel;
  unsigned char version;
  unsigned char hardcfg;
  unsigned reserved1;
  unsigned reserved2;
} BIOSINFO;

void main(void)
{
  void far *biosdate;
  BIOSINFO far *binfo;
  int i;
  union REGS rg;
  struct SREGS srg;

  biosdate = (void far*)MK_FP(0xf000, 0xfff5);

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

  for(i = 0; i < 8; i++)
    putch(*((char far*)biosdate + i));

  rg.h.ah = 0xc0;
  int86x(0x15, &rg, &rg, &srg);
  binfo = (BIOSINFO far*)MK_FP(srg.es, rg.x.bx);

  printf("\nКод модели:                %02.2X"
         "\nДополнительный код модели: %d"
         "\nВерсия изменений BIOS:     %d"
         "\nКонфигурация оборудования: %02.2X\n",
         binfo->model, binfo->submodel,
         binfo->version, binfo->hardcfg);
}

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