Методы защиты от исследования программ 


Мы поможем в написании ваших работ!



ЗНАЕТЕ ЛИ ВЫ?

Методы защиты от исследования программ



МЕТОДЫ ЗАЩИТЫ ОТ ИССЛЕДОВАНИЯ ПРОГРАММ

Технологии противодействия заметно обгоняют эволюцию систем защиты

Крис Касперски. Техника и философия

хакерских атак

 

· Средства исследования программ

· Защита программ от дисассемблирования

· Защита программ от работы под контролем отладчика

 

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

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

 

Средства исследования программ

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

 

Дисассемблирование - это получение из исполняемого кода программы код на языке ассемблера.

 

Дисассемблер - программа, осуществляющая дисассемблирова­ние.

 

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

 

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

 

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

 

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

· шестнадцатеричные просмотрщики - редакторы;

· редакторы таблиц экспорта/импорта;

· так называемые файловые мониторы, позволяющие отслеживать операции работы с файлами;

· а также мониторы реестра, создающие протокол обращений к реестру

· и многие другие.

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

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

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

!

Тем не менее отказываться от использования приемов и методов защиты от дисассемблирования кода программы и ее работы под отладчиком нельзя.

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

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

Защита программ от дисассемблирования

Универсальным методом противодействия дисассемблированию программы является шифрование. Очевидно, что дисассемблирование зашифрованного кода бесполезно.

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

Рекомендуется использовать шифрование с открытым ключом (алгоритм RSA, шифр Эль-Гамаля и др.). В этом случае возможная удачная попытка расшифровать код и понять логику работы защитного механизма не позволит внести изменения в защищенный код, так как для полноценной последующей работы программы эти изменения необходимо внедрить в код в зашифрованном виде. А нарушителю доступен лишь ключ для расшифровки. Возможная атака в данном случае - нахождение «секретного» ключа с помощью трудоемких математических вычислений в зависимости от используемого алгоритма шифрования.

Усиливает защиту динамическое шифрование и многопроходная расшифровка кода.

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

Широко распространены на практике методы, основанные на динамическом изменении кода программы в процессе выполнения. Суть этих методов сводится к получению истинных исполнимых команд на этапе выполнения программы путем некоторого преобразования первоначальных кодов. Часто этот способ защиты называют самогенерируемыми, или самомодифицирующимися кодами. Авторы предлагают различные преобразования, например,

·перемещения участков кода;

· всевозможные функции от истинного кода (контрольной суммы истинного кода);

· или для генерации кода одного участка используют коды предыдущего (или какого-нибудь другого) участка программы (так называемая обратная связь).

Интересный прием защиты от дисассемблирования - использование нестандартной структуры программы. В этом случае дисассемблер «не поймет» нестандартную сегментацию программы.

Подробнее

 

1. Аппаратная отладка - К. Касперски «Техника и философия хакерских атак» [20], стр. 154 - 159.

2. Технологии эмуляции процессора - К. Касперски «Техника и философия хакерских атак» [20], стр. 163 - 167.

 

Назад

Программа FMT256

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

Приведем простую программу FMT256 (листинг 5.1), которая форматирует двадцатую дорожку диска емкостью 1,44 Мбайт в устройстве A:, создавая на ней секторы размером 256 байт.

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

Какую информацию можно записать в нестандартный сектор?

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

Листинг 5.1. Файл fmt256\fmt256.cpp

#include <stdio.h>#include <conio.h>#include <dos.h>#include <stdlib.h>#include <bios.h>#include <string.h> typedef struct _DPT _{ unsigned char srt_hut; unsigned char dma_hlt; unsigned char motor_w; unsigned char sec_size; unsigned char eot; unsigned char gap_rw; unsigned char dtl; unsigned char gap_f; unsigned char fill_char; unsigned char hst; unsigned char mot_start;} DPT; DPT far *get_dpt(void); // Номер форматируемой дорожки#define TRK 20 // Код размера сектора - 256 байт#define SEC_SIZE 1 union REGS inregs, outregs;char diskbuf[512];char diskbuf1[512];char buf[80]; int 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')) return(-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 = 18; // Устанавливаем тип диска 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 = 18; di.buffer = diskbuf; // Подготавливаем буфер формата для 18 секторов for(i=0, j=1; j<19; 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); return(-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; return(0);} /*** get_dpt** Вычислить адрес таблицы параметров дискеты** Функция возвращает указатель на таблицу* параметров дискеты***/DPT far *get_dpt(void){ void far * far *ptr; ptr = (void far * far *)MK_FP(0x0, 0x78); return(DPT far*)(*ptr);}

Программа FMT81TRK

Другой пример - использование нестандартного номера дорожки. Программа FMT81TRK (листинг 5.2) форматирует дорожку (стандартным образом) с номером 81. Обычно считается, что дискеты могут содержать 40 или 80 дорожек, соответственно, с номерами 0...39 или 0...79, однако возможно использование и дорожек с большими номерами. Обычные программы копирования будут копировать только 40 или 80 дорожек, "не заметив" нашей лишней дорожки.

Этим мы и воспользуемся, записав на 81 дорожку контрольную информацию. Для разнообразия в примере используем функции GENERIC IOCTL. Запись этой информации, а также ее чтение и отображение выполняется программой RW82TRK, описанной в следующем разделе.

Листинг 5.2. Файл fmt81trk\fmt81trk.cpp

#include <dos.h>#include <stdio.h>#include <conio.h>#include <malloc.h>#include <errno.h> 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 _TRK_LY_{ unsigned no; unsigned size;} TRK_LY; typedef struct _DPB_{ char spec; char devtype; unsigned devattr; unsigned numofcyl; char media_type; EBPB bpb; char reserved[6]; unsigned trkcnt; TRK_LY trk[100];} DPB; typedef struct _DPB_FORMAT_{ char spec; unsigned head; unsigned track;} DPB_FORMAT; int 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')) return(-1); // Заказываем память для блока параметров устройства dbp = (DPB far*)farmalloc(sizeof(DPB)); // Заказываем память для блока параметров устройства, // который будет использован для форматирования dbp_f = (DPB_FORMAT far*) farmalloc(sizeof(DPB_FORMAT)); if(dbp == NULL || dbp_f == NULL) { printf("\nМало памяти"); return(-1); } // Получаем текущие параметры диска А: dbp->spec = 0; reg.x.ax = 0x440d; reg.h.bl = 1; reg.x.cx = 0x0860; reg.x.dx = FP_OFF(dbp); segreg.ds = FP_SEG(dbp); intdosx(&reg, &reg, &segreg); if(reg.x.cflag!= 0) { printf("\nОшибка: %d", reg.x.ax); return(-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(&reg, &reg, &segreg); if(reg.x.cflag!= 0) { printf("\nОшибка: %d", reg.x.ax); return(-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(&reg, &reg, &segreg); if(reg.x.cflag!= 0) { printf("\nОшибка: %d", reg.x.ax); return(-1); } // Если указанный формат дорожки поддерживается, // поле специальных функций будет содержать 0. // Проверяем это if(dbp_f->spec!= 0) { printf("\nФормат дорожки не поддерживается"); return(-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(&reg, &reg, &segreg); if(reg.x.cflag!= 0) { printf("\nОшибка: %d", reg.x.ax); return(-1); } // Освобождаем память farfree(dbp); farfree(dbp_f); return(0);}

Программа RW81TRK

Для записи и последующего чтения информации на дополнительную дорожку, созданную предыдущей программой, можно использовать программу RW81TRK (листинг 5.3).

Листинг 5.3. Файл fmt81trk\fmt81trk.cpp

#include <dos.h>#include <stdio.h>#include <string.h>#include <malloc.h>#include <errno.h> typedef struct _DPB_WR_{ char spec; unsigned head; unsigned track; unsigned sector; unsigned sectcnt; void _far *buffer;} DPB_WR; char buf[1000];char buf1[80]; int main(void){ union REGS reg; struct SREGS segreg; DPB_WR far *dbp_wr; int sectors, i; // Заказываем память для блока параметров // устройства,который будет // использован для чтения и записи dbp_wr = (DPB_WR far*)farmalloc(sizeof(DPB_WR)); if(dbp_wr == NULL) { printf("\nМало памяти"); return(-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(&reg, &reg, &segreg); if(reg.x.cflag!= 0) { printf("\nОшибка при записи: %d", reg.x.ax); return(-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(&reg, &reg, &segreg); if(reg.x.cflag!= 0) { printf("\nОшибка при чтении: %d",reg.x.ax); return(-1); } printf("\nПрочитано из нестандартного " "сектора:\n%s\n", buf); // Освобождаем память farfree(dbp_wr); return(0);}

Программа FMTINTRL

Более интересный способ защиты дискет от копирования связан с использованием при форматировании нестандартного чередования секторов на дорожке. В программе FMTINTRL (листинг 5.4) использовано "обратное" расположение секторов - вначале идет сектор с номером 15, затем 14 и т. д.

Листинг 5.4. Файл fmtintrl\fmtintrl.cpp

#include <stdio.h>#include <conio.h>#include <dos.h>#include <stdlib.h>#include <bios.h> // Номер форматируемой дорожки#define TRK 20 // Код размера сектора - 512 байт#define SEC_SIZE 2 typedef struct _DPT _{ unsigned char srt_hut; unsigned char dma_hlt; unsigned char motor_w; unsigned char sec_size; unsigned char eot; unsigned char gap_rw; unsigned char dtl; unsigned char gap_f; unsigned char fill_char; unsigned char hst; unsigned char mot_start;} DPT; DPT far *get_dpt(void); union REGS inregs, outregs;char diskbuf[512]; int 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')) return(-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 = 18; // Устанавливаем тип диска 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 = 18; di.buffer = diskbuf; // Подготавливаем буфер формата для 18 секторов // Используем обратный порядок расположения секторов // на дорожке for(i=0, j=18; 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; return(0);} /*** get_dpt** Вычислить адрес таблицы параметров дискеты** Функция возвращает указатель на таблицу* параметров дискеты***/DPT far *get_dpt(void){ void far * far *ptr; ptr = (void far * far *)MK_FP(0x0, 0x78); return(DPT far*)(*ptr);}

Программа CHKINTRL

Для анализа используемого чередования секторов можно использовать программу CHKINTRL (листинг 5.5), которая пытается прочитать подряд два расположенных рядом сектора с номерами 1 и 2. Если используется стандартное чередование, то секторы с номерами 1 и 2 находятся рядом. Если же дорожка отформатирована приведенной выше программой, то эти секторы находятся на максимальном удалении друг от друга.

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

Листинг 5.5. Файл chkintrl\chkintrl.cpp

#include <stdio.h>#include <conio.h>#include <bios.h>#include <dos.h>#include <stdlib.h>#include <time.h> char diskbuf[1024]; int 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("Время для головки 1: %5.1f\n",t2); return 0;}

Программа 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 адрес таблицы конфигурации:

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

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

Бит Описание
  Зарезервировано
  Если этот бит установлен, компьютер оборудован шиной Micro Channel, в противном случае используется шина ISA, PCI или EISA
  Используется расширенная область данных BIOS
  BIOS способна ожидать внешние события
  Каждый раз после вызова прерывания от клавиатуры INT 9h вызывается функция 4Fh прерывания INT 15h
  В компьютере есть часы реального времени
  Имеется второй контроллер прерываний
  Для работы с диском 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);}

МЕТОДЫ ЗАЩИТЫ ОТ ИССЛЕДОВАНИЯ ПРОГРАММ

Технологии противодействия заметно обгоняют эволюцию систем защиты

Крис Касперски. Техника и философия

хакерских атак

 

· Средства исследования программ

· Защита программ от дисассемблирования

· Защита программ от работы под контролем отладчика

 

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

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

 



Поделиться:


Последнее изменение этой страницы: 2016-09-19; просмотров: 478; Нарушение авторского права страницы; Мы поможем в написании вашей работы!

infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 13.58.197.26 (0.061 с.)