TURBO PASCAL |
Новости
|
7.3. ЗАЩИТА ПРОГРАММ ОТ ТРАССИРОВКИАнализ индивидуальных признаков программно—аппаратной среды, в которой работает программа, дает ей возможность проверить легальность копии и принять решение о характере дальнейшей работы. Вот тут—то и кроется наиболее уязвимое место любой системы защиты. Действительно, сама задача защиты программ по сути своей дихотомична, т.е. предполагает выбор одного из двух возможных вариантов: либо копия легальна, либо нет. Выбор либо—либо в конечном счете реализуется машинной инструкцией условного перехода: перейти, если больше (меньше, равно и т.п.). Если в коде программы заменить эту инструкцию на команду безусловного перехода, программа всегда будет работать так, как если бы проверка легальности копии дала положительный результат. Таким образом, каким бы хитроумным не был алгоритм проверки, он легко нейтрализуется, если в точках дихотомического ветвления программы заменить инструкции условного перехода на команды безусловного перехода. Если Вы хотите защитить свою программу от нелегального копирования, Вы должны составить ее таким образом, чтобы всемерно усложнить работу нескромному наблюдателю, пытающемуся проследить логику ее работы и отыскать ключевые точки ветвления. Фактически это означает вести борьбу с человеком, осуществляющим дизассемблирование программы (т.е. перевод машинных кодов в инструкции ассемблера) и ее трассировку (прослеживание логики работы). В теоретическом плане такую борьбу можно считать проигранной — ведь в конечном счете человек всегда умнее машины. Однако здесь уместна, как мне кажется, параллель с шахматами: известно, что теоретическое положение «белые начинают и выигрывают» вовсе не означает абсолютной бесперспективности черных! Точно также имеет шансы устоять от попыток «взлома» и грамотно составленная система защиты. Суть борьбы со «взломщиком» заключается в том, что многочисленные программные «ловушки», блокирующие работу программ—трассировщиков, и крайне запутанная логика работы системы защиты могут сделать ее «взлом» весьма трудным делом даже для опытного взломщика. Для дизассемблирования программ обычно используются различные программы— дизассемблеры, преобразующие машинные коды в текстовые строки ассемблерных инструкций. Чтобы затруднить дизассемблирование, можно зашифровать часть программы. В этом случае в работающей про — грамме осуществляется необходимая дешифровка этих фрагментов перед тем, как им будет передано управление, а после дизассемблирования исходного ЕХЕ— файла ключевые фрагменты программы оказываются совершенно бессмысленными. Реализация этой идеи на Турбо Паскале не так проста, как это может показаться на первый взгляд. Дело в том, что компилятор Турбо Паскаля создает ЕХЕ—фэ&л, содержащий многочисленные адресные ссылки с использованием относительной адресации. В процессе загрузки файла в оперативную память осуществляется замена относительных адресов на абсолютные. Шифровка может нарушить относительную адресацию и дешифрованная программа потеряет свою работоспособность. Выходом из положения может служить разработка на ассемблере зашифрованных СОМ— программ, вызываемых из основной программы и обменивающихся с нею данными через общее поле памяти. Ссылку на это поле можно поместить в заранее обусловленный и неиспользуемый ДОС вектор прерывания. Замечу, что более или менее сложная Турбо Паскалевая программа насчитывает многие десятки, а часто и сотни килобайт и после дизассемблирования дает гигантские листинги, объемом во многие сотни тысяч и даже миллионы символов. Разобраться в этой мешанине кодов без пошаговой трассировки практически невозможно, а следовательно, можно не тратить силы на шифровку громоздких программ. Защита от трассировки заключается в том, что программа распознает факт пошаговой трассировки и пытается тем или иным способом противодействовать этому процессу. Чаще всего для этих целей используется остроумный прием, основанный на особенностях архитектуры микропроцессоров семейства Intel 80х86. Для повышения производительности этих кристаллов в них применяется конвейерный способ обработки команд, в соответствии с которым очередная инструкция загружается во внутреннюю очередь команд процессора до завершения обработки текущей инструкции. Если текущая инструкция изменяет следующую за ней командуг то при реальной работе программы это изменение не сказывается на алгоритме, так как к этому моменту содержимое изменяемой ячейки памяти уже загружено в очередь команд. Если же программа работает под управлением трассировщика, внутренняя очередь оказывается заполненной командами трассировщика и модификация ячейки приводит к изменению алгоритма работы. Следующая программа иллюстрирует описанный прием. В ассемблерной процедуре TrassBlock модифицируется однобайтная команда RET выхода из процедуры на команду NOP (пустая команда). Если программа управляется трассировщиком, произойдет реальная замена команды и вступит в действие остальная часть процедуры, которая реализует «горячую» перезагрузку ДОС (используется недокументированное соглашение о том, что точка входа в BIOS— программу перезапуска имеет адрес $FOOO:$FFFO; фирмы—изготовители BIOS не обязаны следовать этому правилу). Чтобы убедиться в этом, запустите программу под управлением какого-либо отладчика, например, Turbo Debugger, и проследите ее по шагам после вызова CALL 0000 — это и есть вызов процедуры TrassBlock. Более простой способ — пошаговое прослеживание программы в среде Турбо Паскаль с помощью клавиши F7, однако в этом случае будет нарушена «чистота эксперимента» — ведь у взломщика нет исходного текста программы. ^-———————————————————————+ I Иллюстрация использования процедуры \ ] блокировки режима пошаговой \ ¦ трассировки программы I -г———————.——-————— ———————+/ Procedure TrassBlock; Assembler; {Процедура реализует вызов программы перезапуска ДОС в случае, если она работает под управлением трассировщика} ASM {Модификация команды по метке @} mov BYTE PTR @,90h {Команда NOP} {Следующая команда не модифицируется,ели нет трассировки, в противном случае она заменяется командой NOP} @: ret {Сюда управление передается только в том случае, когда работа программы прослеживается трассировщиком} mov ax,$FOOO {Сегмент точки перезапуска} push ax mov ax,$FFFO {Смещение точки перезапуска} push ax retf {Перезапуск ДОС} end; {TrassBlock} begin TrassBlock end. Как еще программа может определить факт трассировки? Пожалуйста: • с помощью контроля отладочных прерываний INT 1 и INT 3; • с помощью замера времени выполнения некоторого эталонного участка программы; • с помощью перехвата прерывания от клавиатуры; • с помощью перехвата прерывания от таймера. В архитектуре микропроцессора предусмотрены два отладочных прерывания с векторами 1 и 3. Первое активизируется после выполнения каждой очередной инструкции программы, если взведен флаг трассировки TF. Второе вызывается однобайтной командой INT и реализует останов в контрольной точке. Сразу после загрузки ДОС эти векторы указывают на расположенные в ПЗУ программы обработки прерываний, которые ничего не делают и просто возвращают управление прерванной программе. Таким образом, если сегментная часть этих векторов указывает на ОЗУ (меньше $АООО), есть большая вероятность того, что программа прослеживается (может оказатьсяг что векторы изменены какой—то ранее запускавшейся программой, но не восстановлены после ее завершения). Чтобы убедиться в этом, программа может проанализировать флаг TF режима трассировки и/ или попытаться заменить эти векторы так, чтобы они указывали на соОственную процедуру обработки. Например, следующая программа использует небольшой фрагмент, написанный на встроенном ассемблере, чтобы проанализировать флаг трассировки. В результате анализа на экран выводится сообщение «Нет трассировки» или «Есть трассировка». Замечу, что некоторые отладчики, в том числе — встроенный отладчик Турбо Паскаля и мой любимый Turbo Debugger, не используют аппаратной трассировки по прерыванию INT 1 и, следовательно, программа, работающая под их управлением, не обнаруживает факт трассировки. Чтобы это все-таки произошло, трассируйте программу стандартным отладчиком Debug. I Программа проверяет флаг трассировки ¦ I TF и выводит соответствующее сообщение \ +——.——————————________————————+; Uses DOS; var Flags: Word; {Состояние флагов МП} begin Flags := 0; asm pushf {Помещаем флаги в стек} pop ax {Извлекаем их в АХ} mov Flags,ax {Пересылаем в FLAGS} end; if (Flags and $100)=0 then WriteLn('Нет трассировки') else WriteLn('Есть трассировка') end. Замер времени выполнения эталонного участка программы во многих случаях оказывается более эффективным средством обнаружения трассировки. Для получения отсчетов времени удобно использовать уже упоминавшийся системный счетчик $0000:$046С. Например: ^————————————————————————+ ¦ Контроль трассировки с помощью замера \ ¦ времени выполнения участка программы \ +—————.——————————————————————+; var tl,t2: Longint; begin t2 := 0; {Сбрасываем счетчик} tl := MemL[0:$046С]; {Получаем начальный момент времени} while MemL[0:$046C]=tl do; {Ждем начала очередного 55-мс интервала} {Цикл измерения} while MemL[0:$046C]<tl+2 do inc(t2) ; {Анализируем результат} if t2<500 then WriteLn('Есть трассировка') else WriteLn (t2) end. Проверка if t2<500 then учитывает минимальную производительность компьютера класса IBM PC с тактовой частотой 4.77 МГц (около 780 единиц для этой программы). Перехват прерывания от клавиатуры использует тот очевидный факт, что для управления трассировщиком взломщик должен использовать клавиатуру. Для обнаружения этого программа переназначает вектор прерывания $09 от клавиатуры на собственную процедуру, после чего выполняет достаточно длинный цикл. Если в ходе выполнения цикла будет нажата любая клавиша, управление получит новый обработчик прерывания $09 и таким образом программа сможет обнаружить факт трассировки. В следующем примере процедура обработки прерывания $09 осуществляет «горячую» перезагрузку ДОС: _•+—— —————__— —__-——____—____—+ ¦ Блокировка трассирования программы ¦ ¦ с помощью контроля прерывания от ] ¦ клавиатуры \ +————.————.————————————————+_ Oses DOS; var 01dlnt9: pointed-Procedure Int9; Assembler; {Получает управления после нажатия на любую клавишу и перезагружает ДОС} ASM mov ax, $FOOO push ax mov ax, $FFFO push ax retf end; var k,n: Longint; begin SwapVectors; GetIntVec($9,01dlnt9) ; SetIntVec($9,@Int9) ; for k := 1 to 20000 do inc (n) ; SetIntVec($9,01dlnt9) ; WriteLn('HeT трассировки') end. Приблизительно таким же способом можно использовать прерывание от таймера $16. Перед заменой векторов программа должна запретить прерывания командой CL1, затем выполнить цикл. Если программа не трассируется, управление процедуре обработки прерывания не будет передано ни разу. Напротив, трассировщик обязательно откроет прерывания и тем самым обнаружит себя. |
(с)Все права защищены По всем интересующим вопросам прошу писать на электронный адрес |