защита программ

ГЛАВА V
 
МЕТОДЫ ЗАЩИТЫ ОТ ИССЛЕДОВАНИЯ ПРОГРАММ
 
Технологии противодействия заметно обгоняют эволюцию систем защиты
Крис Касперски. Техника и философия
хакерских атак
 

·      [ Cкачайте файл, чтобы посмотреть ссылку ]

·      [ Cкачайте файл, чтобы посмотреть ссылку ]

·      [ Cкачайте файл, чтобы посмотреть ссылку ]
 
Противодействие попыткам запуска и/или исполнения защищенной программы обязательно необходимо сопровождать механизмами, предотвращающими возможность исследования программы, в том числе и зарегистрированных (законных) копий.
Повторим, что для взлома защиты нарушителю прежде всего необходимо найти защитный механизм и понять логику его работы, то есть исследовать программу.
 
[ Cкачайте файл, чтобы посмотреть ссылку ]
 
Основными инструментами для исследования программ являются дисассемблеры и отладчики.
 
Дисассемблирование - это получение из исполняемого кода программы код на языке ассемблера.
 
Дисассемблер - программа, осуществляющая дисассемблирование.
 
Интерактивный дисассемблер - программа, тесно взаимодействующая с пользователем в процессе дисассемблирования.
 
Отладчик - программа, предназначенная для анализа поведения другой программы, обеспечивающая остановку в указанных точках и позволяющая просматривать (редактировать) содержимое ячеек памяти, регистров процессора и команды программы.
 
Эмулирующий отладчик - отладчик, который самостоятельно интерпретирует и выполняет команды программы (без использования реального процессора).
 
Существует также множество программ-утилит, предназначенных для вспомогательных операций по изучению логики работы механизма защиты. Широко используются

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

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

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

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

·      и многие другие.
Например, с помощью файлового монитора (FileMonitor) взломщик может отследить работу защищенной программы с файлами и обнаружить ключ (пароль), хранящийся в некотором файле. Произведя анализ протокола обращений к реестру с помощью монитора реестра (RegMon), взломщик может обнаружить ключ (пароль), хранящийся в системной базе данных Registry.
Инструментарий современного хакера на столько развит, что все попытки авторов защит противодействовать исследованию программ с точки зрения высококвалифицированных хакеров считаются безрезультатными.
С помощью современных версий интерактивных дисассемблеров и эмулирующих отладчиков может быть обнаружена практически любая защита.
!
Тем не менее отказываться от использования приемов и методов защиты от дисассемблирования кода программы и ее работы под отладчиком нельзя.
Напомним, что абсолютной защиты вообще не бывает. Эффективной защитой считается такая, на взлом которой необходимы материальные и трудовые затраты, во много раз превышающие затраты на покупку программного обеспечения. Поэтому затруднение взлома защиты любыми путями и методами является оправданным. Если преодолеть защиту не сможет молодой неопытный взломщик, и заказчику придется обращаться к высококвалифицированному специалисту, - это уже плюс.
Считая защиту от дисассемблирования и отладки, рассчитанной на взломщика средней квалификации, назовем такую защиту затруднением анализа программ. Рассмотрим ее основные моменты.
 
[ Cкачайте файл, чтобы посмотреть ссылку ]
 
Универсальным методом противодействия дисассемблированию программы является шифрование. Очевидно, что дисассемблирование зашифрованного кода бесполезно.
Применяя шифрование кода программы для противодействия дисассемблированию, следует учитывать распространенные ошибки реализации данного метода. Напомним их. Во-первых, неэффективной является такая реализация, когда исполняемый код в полном объеме и однократно шифруется / дешифруется (так как легко найти момент после дешифрования). Во-вторых, необходимо осуществить выбор эффективного ключа и, если необходимо, надежно хранить ключ. И в-третьих, следует учесть, что для защиты программ от дисассемблирования, не рекомендуется использование симметричных криптографических алгоритмов.
Рекомендуется использовать шифрование с открытым ключом (алгоритм RSA, шифр Эль-Гамаля и др.). В этом случае возможная удачная попытка расшифровать код и понять логику работы защитного механизма не позволит внести изменения в защищенный код, так как для полноценной последующей работы программы эти изменения необходимо внедрить в код в зашифрованном виде. А нарушителю доступен лишь ключ для расшифровки. Возможная атака в данном случае - нахождение «секретного» ключа с помощью трудоемких математических вычислений в зависимости от используемого алгоритма шифрования.
Усиливает защиту динамическое шифрование и многопроходная расшифровка кода.
На практике неплохо зарекомендовали себя и методы, использующие вместе с шифрованием архивирование программного кода. К достоинствам данного метода относят и уменьшение размера исполняемого файла. Однако следует учитывать, что алгоритмы работы широко используемых архиваторов известны многим взломщикам.
Широко распространены на практике методы, основанные на динамическом изменении кода программы в процессе выполнения. Суть этих методов сводится к получению истинных исполнимых команд на этапе выполнения программы путем некоторого преобразования первоначальных кодов. Часто этот способ защиты называют самогенерируемыми, или самомодифицирующимися кодами. Авторы предлагают различные преобразования, например,

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

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

· или для генерации кода одного участка используют коды предыдущего (или какого-нибудь другого) участка программы (так называемая обратная связь).
Интересный прием защиты от дисассемблирования - использование нестандартной структуры программы. В этом случае дисассемблер «не поймет» нестандартную сегментацию программы.
 
[ Cкачайте файл, чтобы посмотреть ссылку ]
 
Для того, чтобы лучше понять методы борьбы с отладчиками и пути увеличения эффективности этих методов, напомним суть процесса работы программы под отладчиком.
Существует два отладочных механизма:
1)   контрольные точки останова и
2)   трассировка программы.
Идея первого механизма заключается во внесении в программный код специального однобайтового кода (0xСС) - так называемой контрольной точки останова. Заметим, что можно внести в программный код любое количество таких точек. Во время выполнения программы при достижении контрольной точки останова возникает исключительная ситуация - прерывание int 3. В этот момент процессор останавливает работу программы для дальнейших распоряжений пользователя. Для того, чтобы позже продолжить работу программы с точки останова, в стеке запоминаются значения регистра флагов, регистра CS (указатель текущего кодового сегмента) и регистра IP (указатель на следующую выполнимую команду). При этом сбрасывается флаг трассировки.
Итак, для анализа программы с помощью отладчика в исполняемый код программы необходимо внести контрольные точки останова, следовательно, изменить код!
Этим фактом успешно пользовались авторы защит: достаточно было обнаружить модификацию кода и либо удалить точку останова, либо прекратить дальнейшее выполнение программы.
Для обнаружения модифицированного кода традиционно применялись следующие методы:

·      подсчет контрольных сумм критических участков;

·      использование контрольной суммы всего кода для расшифровки некоторого фрагмента;

·      многопроходная расшифровка кода с ключом, вычисляемым на основе контрольной суммы всего кода либо критического участка;

·      использование корректирующих кодов, позволяющих определить местоположение контрольного байта;

·      контроль времени выполнения критического участка по сравнению с эталонным временем;

·      контроль относительного времени выполнения участка программы (относительно другого участка)

·      и другие.
Интересные методы противодействия отладчикам реального (!) режима основаны на перехвате необходимого отладчику прерывания int 3. Предлагается заменить команды обработчика прерывания int 3 «мусором» либо использовать обработчик прерывания в целях защиты, например, для расшифровки кода. Тогда в первом случае отладчик будет просто нейтрализован, а во втором - возникнет конфликт между механизмом защиты и отладчиком.
Существует целая группа приемов, основанных на использовании отладчиком стека.
Одним из таких приемов является следующий. При выполнении критического участка необходимо присвоить указателю стека нулевое значение. Таким образом, стек считается полным и не может быть использован для сохранения необходимых регистров. Операционная система в таком случае завершает работу приложения.
Другой прием - хранить в стеке (полностью используя стек) данные, необходимые для работы программы. Отладчик, при использовании стека для записи значений необходимых регистров, удалит (затрет) критические данные, и программа станет неработоспособной.
Рассмотрим второй отладочный механизм - трассировку кода программы.
 
Трассировка - это пошаговое выполнение программы.
 
Установка специального флага трассировки (TF) приводит к генерированию после каждой команды исключительной ситуации - прерывания int 1, называемого трассировочным прерыванием.
При этом, аналогично обработке исключительной ситации int 3, в стеке сохраняются необходимые для дальнейшей работы значения регистра флагов и регистра IP (указатель на следующую выполнимую команду).
Поэтому наряду с простыми проверками - установлен ли флаг трассировки - для противодействия трассировке могут быть использованы и перечисленные выше приемы.
Понятно, что трассировщик будет стараться следить за флагом TF и корректировать его. Поэтому необходимо для получения оригинального флага трассировки использовать специальные приемы.
Проверять установку флага трассировки можно, исходя из аппаратных особенностей процессора, например, используя потерю трассировочного прерывания. Этот метод базируется на том, что после команд, изменяющих сегментный регистр SS (до процессора Intel 80386 - любой сегментный регистр), не происходит трассировочное прерывание даже при установленном флаге трассировки. Чтобы получить истинное значение флага трассировки, достаточно перед проверкой регистра флагов переслать сегментный регистр SS.
Для усиления защиты от пошагового выполнения программы авторы успешно используют значение флага трассировки (в совокупности с другими параметрами) для расшифровки критических участков или, что еще сложнее для взлома, в арифметических выражениях.
Современные операционные системы и отладчики позволяют установить аппаратные точки останова. Аппаратная отладка основана на использовании специальных отладочных регистров (всего 8 регистров) и возможности простановки четырех контрольных точек останова.
!
Аппаратные точки останова никак не модифицируют код и, следовательно, не могут быть обнаружены традиционными средствами.
Однако существуют приемы, позволяющие, в принципе, обнаружить работу программы под аппаратной отладкой. Для этого можно использовать все отладочные регистры для нужд программы или поместить в отладочные регистры «мусор». Усиливается защита использованием всех четырех контрольных точек останова. Но реализация таких приемов требует высокой квалификации программиста и вряд ли оправдана, так как для взлома защиты нарушитель может воспользоваться другими технологиями и инструментами.
Для взлома механизмов защиты сегодня применяются мощные эмулирующие отладчики. Они самостоятельно (без помощи процессора) интерпретируют и выполняют команды исследуемой программы. Существуют так называемые отладчики с неполной эмуляцией. Эти отладчики интерпретируют только некоторые команды, а остальные выполняют на реальном процессоре.
Очевидно, что против таких отладчиков бессильны любые приемы противодействия. Поэтому «сегодня уже мало кто решается противодействовать отладчику и включать в свое приложение антиотладочный код. Мода на это давно прошла» [20, с. 140].
Единственным средством борьбы с отладкой сами хакеры называют эмуляторы процессора.
___________________________________________________________
 
Подробнее
 
1.    Аппаратная отладка - К. Касперски «Техника и философия хакерских атак» [20], стр. 154 - 159.
2.    Технологии эмуляции процессора - К. Касперски «Техника и философия хакерских атак» [20], стр. 163 - 167.
 
[ Cкачайте файл, чтобы посмотреть ссылку ]
5. Защита программ от несанкционированного копирования
[ Cкачайте файл, чтобы посмотреть ссылку ]
[ Cкачайте файл, чтобы посмотреть ссылку ]
[ Cкачайте файл, чтобы посмотреть ссылку ]
В этой главе мы рассмотрим некоторые способы организации защиты программ от несанкционированного копирования. Практически все эти способы используют специальные методы работы с дисками, поэтому мы и расположили данный материал в книге, посвященной дисковой системе.
Прежде чем начинать проектирование системы защиты от несанкционированного доступа, нужно совершенно четко себе представлять, что именно и от кого вы собираетесь защищать. Это необходимо для правильного выбора средств защиты.
Не существует никаких "абсолютно надежных" методов защиты. Можно утверждать, что достаточно квалифицированные системные программисты, пользующиеся современными средствами анализа работы программного обеспечения (отладчики, дизассемблеры, перехватчики прерываний и т. д.), располагающие достаточным временем, смогут преодолеть практически любую защиту. Этому способствует "открытость" операционной системы MS-DOS, которая хорошо документирована и предоставляет любой программе доступ к любым программным и аппаратным ресурсам компьютера.
Поэтому при проектировании системы защиты следует исходить из предположения, что рано или поздно эта защита окажется снятой. Целью проектирования, по мнению авторов книги, должен быть выбор такого способа защиты, который обеспечит невозможность несанкционированного копирования для заранее определенного круга лиц и в течение ограниченного времени.
Например, если вы собираетесь защитить от копирования коммерческую версию вашей программы, вам необязательно защищать эту версию от копирования "навсегда" - стоимость такой защиты может превысить стоимость самой программы. Вполне достаточно, чтобы способ защиты было невозможно "разгадать" к моменту появления следующей версии вашей программы, так как в новой версии вы сможете изменить этот способ.
Короче говоря, уровень защиты должен быть таким, чтобы было бы выгоднее купить программу, а не заниматься снятием защиты от копирования.
Иногда защищать программы от копирования вообще нецелесообразно. Фирма Microsoft и многие другие фирмы не защищают свои программные продукты от копирования. Для привлечения покупателей эти фирмы устанавливают низкие цены на свои изделия и, что более важно, обеспечивают сопровождение на высоком уровне и высококачественную документацию. Новые версии программ продаются зарегистрированным пользователям со значительными скидками. Эти версии появляются достаточно быстро, поэтому имеет смысл покупать новые версии, а не копировать старые.
Таким образом, для выбора способа организации защиты от копирования необходим индивидуальный подход в каждом конкретном случае.
Все средства защиты можно разделить на аппаратные и программные.
Аппаратные средства защиты могут быть реализованы в виде специальных модулей, устанавливаемых в слоты расширения материнской платы компьютера, либо подключаемых к последовательному или параллельному порту. Эти модули могут содержать однокристальные микро-ЭВМ или специальные заказные микросхемы, выполняющие обмен кодовыми последовательностями с программой. Можно также использовать специальные версии BIOS.
В нашей книге мы будем рассматривать только программные средства защиты, так как они доступны, не требуют специального оборудования и вместе с тем дают неплохие результаты.
Мы опишем способы защиты дискет от копирования, а также защиты от копирования программ, записанных на жестком диске.
Кроме того, небольшой раздел этой главы посвящен вопросам защиты программ от отладки. Те участки программ, которые отвечают за защиту, желательно составлять таким образом, чтобы они работали по-разному в зависимости от того, выполняются они в реальном времени, или под управлением какого-либо отладчика.
Приведенный в этой главе перечень способов защиты не претендует на полноту, мы продемонстрируем лишь основные приемы защиты и приведем некоторые программы. Проблема защиты информации неисчерпаема, приступая к ее решению, помните о вечной борьбе брони и снаряда.
5.1. Защита дискет от копирования
Дискета, предназначенная для установки защищенного от копирования программного обеспечения должна быть сама защищена от копирования.
Копирование дискет можно выполнить как по файлам (с помощью команд операционной системы COPY или XCOPY), так и по секторам (командой DISKCOPY, программами PCTOOLS, PCSHELL и аналогичными).
Кроме того, существуют программы, специально предназначенные для копирования дискет, защищенных от копирования, например COPY2PC, TeleDisk. Специальные программы могут копировать дискеты, содержащие только определенные защищенные программные пакеты, или они могут повторять структуру дорожек диска с точностью до бита.
Наиболее просто обеспечить защиту от программ копирования дискет по секторам. Можно предложить следующие достаточно простые способы, использующие нестандартное форматирование отдельных дорожек дискеты:
форматирование отдельных дорожек с размером секторов, отличным от стандартного для MS-DOS, например, 128 или 1024 байт;
создание дорожек за пределами рабочей зоны диска, например, создание 41 дорожки для дискеты емкостью 360 Кбайт или 81 дорожки для дискеты емкостью 1,44 Мбайт;
создание большего, чем стандартное, количества секторов на дорожке;
форматирование отдельных дорожек с использованием фактора чередования секторов с последующим анализом времени доступа к секторам для обычных стандартных дорожек и для нестандартных дорожек;
использование нестандартного символа заполнения при форматировании.
Очевидно, что все эти способы непригодны для защиты от таких программ копирования, которые способны копировать битовую структуру дорожек диска. Что можно порекомендовать в этом случае?
Можно использовать специальную аппаратуру при записи установочных дискет, которая позволяет записывать отдельные дорожки или секторы как бы с промежуточным уровнем записи. Эти участки дорожки будут читаться нестабильно.
Если скопировать такую дискету на обычной аппаратуре (с использованием обычных НГМД и программ битового копирования) то все дорожки будут читаться стабильно. Если при многократном контрольном чтении указанных секторов или дорожек каждый раз будут получены разные данные - мы имеем дело с оригиналом, в противном случае - с незаконной копией.
Однако дискеты с промежуточным уровнем записи все-таки могут быть скопированы с использованием специальной аппаратуры, копирующей содержимое дорожек "аналоговым" способом (как в бытовом магнитофоне).
Для защиты от аналогового копирования можно использовать дискеты, на которых в некоторых местах искусственно созданы дефекты магнитного покрытия - выжженные лазером небольшие точки или просто царапины.
Проверка основывается на том, что в дефектные места невозможно ничего записать. Если мы имеем дело с копией, то на месте дефектных секторов окажутся хорошие - копируется только информация, но не дефекты дискеты!
Разумеется можно использовать комбинации различных методов защиты от копирования. При этом легко распознаваемые методы (нестандартный размер сектора и т. п.) можно использовать для маскировки какого-либо другого, более тонкого метода защиты.
Более подробно мы остановимся на нестандартном форматировании, как на наиболее простом методе защиты от копирования, для использования которого не требуется ни специальной аппаратуры, ни специально подготовленных дискет с дефектами. Используя сведения о работе с диском на физическом уровне, приведенные в этой книге, вы сможете самостоятельно использовать метод дефектных дискет или дискет с промежуточным уровнем записи.
Программа FMT256
Самое простое, что можно сделать для того чтобы защитить установочную дискету от копирования - изменить размер секторов на дорожке.
Приведем простую программу FMT256 (листинг 5.1), которая форматирует двадцатую дорожку диска емкостью 1,44 Мбайт в устройстве A:, создавая на ней секторы размером 256 байт.
После форматирования программа записывает в первый сектор нестандартной дорожки строку, введенную с клавиатуры. Затем для контроля содержимое этого сектора считывается и отображается на экране. Обратите внимание на изменения в таблице параметров дискеты - они необходимы для использования нестандартного размера сектора.
Какую информацию можно записать в нестандартный сектор?
Если вы делаете установочную (инсталляционную) дискету, которая рассчитана на ограниченное количество установок, нестандартный сектор - самое подходящее место для хранения счетчика установок. Даже такие программы, как Norton Disk Editor не помогут прочитать или изменить значение этого счетчика. В этот же сектор можно записать и другую информацию, необходимую для правильной установки защищенного программного обеспечения.

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

#include
#include
#include
#include
#include
#include

typedef struct _DPT _
{
unsigned char s
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
// Номер форматируемой дорожки
#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 ·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
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(®, ®, &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(®, ®, &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(®, ®, &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(®, ®, &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
#include
#include
#include
#include

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(®, ®, &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(®, ®, &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
#include
#include
#include
#include

// Номер форматируемой дорожки
#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
#include
#include
#include
#include
#include

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;
}

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
#include
#include
#include
#include
#include
#include
#include
#include

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(®s, ®s, &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 {
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
#include
#include

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);
}

5.3. Защита программ от трассировки
Защищая свои программы от несанкционированного копирования, не следует забывать о таких средствах "взлома", как пошаговые отладчики - Turbo Debugger , CodeView , Advanced Fullscreen Debug , AT86 и т. п.
Используя отладчики, искушенные в своем деле системные программисты рано или поздно смогут обнаружить и отключить (или обмануть) средства защиты от копирования.
В этом разделе книги мы расскажем о некоторых приемах, позволяющих затруднить обнаружение средств защиты.
Стандартный метод обнаружения средств защиты от копирования - дизассемблирование программы установки или выполнение ее под управлением пошагового отладчика. Листинг, получаемый в процессе дизассемблирования, оказывает большую услугу при использовании отладчика, поэтому эти два средства - дизассемблирование и использование отладчика - обычно используются вместе.
Соответственно, требуются специальные приемы программирования для борьбы с дизассемблером и защиты от отладчиков.
Для затруднения дизассемблирования лучше всего подходит шифрование отдельных участков программ или всей программы целиком. После загрузки программу следует расшифровать в оперативной памяти и передать ей управление. Еще лучше выполнять динамическое расшифровывание программы по мере ее выполнения, когда участки программы расшифровываются непосредственно перед использованием и после использования сразу же уничтожаются.
При расшифровывании можно копировать участки программы в другое место оперативной памяти.
Пусть, например, программа состоит из нескольких частей. После ее загрузки в оперативную память управление передается первой части программы. Эта часть предназначена для расшифровки второй части, которая находится в памяти вслед за первой.
Задача второй части - перемещение третьей части программы на место уже использованной первой части и расшифровка ее там.
Третья часть, получив управление, может проверить свое расположение относительно префикса программного сегмента и, в случае правильного расположения (сразу вслед за PSP), начать загрузку сегментных регистров такими значениями, которые необходимы для выполнения четвертой (установочной) части программы.
Если попытаться дизассемблировать программу, составленную подобным образом, то из этого ничего не получится.
Второй способ борьбы с дизассемблером является, по своей сути, борьбой с человеком, занимающимся дизассемблированием. Он заключается в увеличении размера загрузочного модуля программы до сотни-другой Кбайт и в усложнении структуры программы.
Объем листинга, получающегося при дизассемблировании программы размером в 30 - 40 Кбайт, достигает 1 - 1,5 Мбайт. Поэтому большие размеры установочной программы могут сильно увеличить время обнаружения средств защиты.
Что такое усложнение структуры программы, достаточно понятно само по себе. Авторам известна программа, использующая для обращения к одной и той же области памяти, содержащей многочисленные переменные, разные сегментные адреса. Поэтому очень трудно сразу догадаться, что на самом деле программа работает с одной и той же областью памяти.
Мы не будем дальше рассказывать о способах усложнения структуры программы, оставив простор для фантазии читателя. Вместо этого перейдем к борьбе с трассировкой программы пошаговыми отладчиками.
Можно предложить две группы средств защиты от трассировки. В первую группу входят средства блокировки работы самих отладчиков, делающие невозможным трассировку программы. Вторая группа средств направлена на определение факта работы программы под управлением отладчика.
К первой группе средств относится:
блокировка специальных "отладочных" прерываний процессора;
блокировка прерываний от клавиатуры;
измерение времени выполнения контрольных участков программы;
использование прерывания таймера.
Напомним, что прерывание INT 1 и INT 3 (соответственно, прерывание для пошаговой работы и прерывание по однобайтовой команде INT) интенсивно используются отладчиками. Первое прерывание позволяет отладчику получать управление после выполнения каждой команды трассируемой программы. С помощью второго прерывания отладчик может устанавливать точки останова, заменяя байты программы на команду однобайтового прерывания.
Защищенная от трассировки программа должна подготовить свои собственные обработчики для этих прерываний и "незаметно" для человека, занимающегося трассировкой, записать их адреса в таблицу векторов прерываний.
Эти обработчики прерываний могут не делать ничего, то есть состоять из одной команды IRET, или выполнять какие-либо действия, фиксирующие факт работы программы под контролем отладчика.
В ходе трассировки программы (при ее пошаговом выполнении) вам необходимо нажимать на клавиши - для перехода к очередной команде. Это можно использовать для блокировки отладчика.
Запретив прерывания командой CLI и переназначив прерывание от клавиатуры на себя, программа установки может выполнять какие-либо действия, не требующие работы оператора с клавиатурой. Обработчик клавиатурного прерывания защищенной от трассировки программы должен фиксировать прерывания, например, установкой флага.
Если программа работает не под контролем отладчика, прерывания во время этого участка программы невозможны (они запрещены командой CLI).
Если же используется режим пошагового выполнения программы под управлением отладчика, отладчик разрешает клавиатурные прерывания, невзирая на то, что была выдана команда CLI. Наш обработчик клавиатурного прерывания в этом случае зафиксирует работу в режиме трассировки.
Аналогично можно воспользоваться прерыванием таймера.
Защищенная программа устанавливает свой обработчик для прерывания таймера, который устанавливает флаг в случае появления прерывания от таймера. В подходящий момент времени она запрещает прерывания и выполняет какую-либо работу, периодически проверяя флаг.
Если программа работает в пошаговом режиме, команда запрета прерываний CLI не работает, и флаг будет взведен, сигнализируя работу под контролем отладчика.

Заголовок 1 Заголовок 2 Заголовок 315

Приложенные файлы

  • doc 26448056
    Размер файла: 294 kB Загрузок: 0

Добавить комментарий