Защита от отладчиков

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

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

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

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

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

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

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

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

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

Если попытаться дизассемблировать программу, составленную подобным образом, то из этого ничего не получится.

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

Объем листинга, получающегося при дизассемблировании программы размером в 30-40 килобайт, достигает 1-1.5 мегабайта. Поэтому большие размеры инсталляционной программы могут сильно увеличить время обнаружения средств защиты.

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

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

Int 1 - пошаговый режим выполнения;
Int 3 - точка приостанова.
Ваша задача незаметно, не используя стандартные средства, которые легко отслеживаются как самим отладчиком, так и хакером изменить значения адреса этих векторов. Периодически необходимо проверять занесенные значения. Пошаговый режим выполнения включается при установленном флаге TF процессора. Сброс этого флага в ноль приводит к выключению отладчика. Все отладчики отслеживают команду popf и восстанавливают значение флага TF, но очень мало отладчиков понимают смоделированный возврат из прерывания, когда в стеке находится слово флагов с выключенным флагом трассировки. Аналогично все отладчики отслеживают команду pushf и очищают флаг TF, но большинство отладчиков не понимают команду cs:pushf или es:pushf.

Еще более изящно использовать вектора отладчика в своих целях. Например, вы можете переназначить 21 вектор на 3 и обращаться к MSDOS не через int 21h, а через int 3h это короче на 1 байт и поэтому не позволит взломщику произвести обратную замену.

Слово состояния процессора:

17161514131211109876543210
VM R   NT I/O PL OF DF IF TF SF ZF   AF   PF   FC

VM - виртуальный режим

R - ошибка отладки
NT - вложенная задача
I/O PL - привилегии: 0 - высший, 3 - низший
OF - переполнение
DF - направление
IF - прерывания
TF - ТРАССИРОВКА
SF - знак
ZF - нуль
AF - вспомогат.перенос
PF - четность
FC - перенос

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

Большинство отладчиков используют стек отлаживаемой программы. Вы можете занести в неиспользуемую часть стека некоторую информацию до участка программы, в котором не используется стек, а после этого участка проверить:
		mov	bp,sp
		mov	ax,'a1'
		mov	[bp-2],ax

  ..... 	// не использовать стек!!!

		cmp	word ptr [bp-2],'a1'
		jne	a61	// под отладчиком

При работе в реальном режиме использование инструкций вида mov sp, 1 приводит к генерации int 06(invalid opcode). Вы можете маскировать прерывания на период выполнения критической части программы:
	  CS:0100 E421		 in	al,21h
	  CS:0102 0C02		 or	al,00000010b	;IRQ 1 keyboard irq
	  CS:0104 E621		 out	21h,al
или
	  CS:0100 E461		 in	al,61
	  CS:0102 0C80		 or	al,10000000b	; bit 7 - disable kbd
	  CS:0104 E661		 out	61h,al
или
	  CS:0100 B4AD		 mov	al, 0ADh	; disable keyboard
	  CS:0102 E664		 out	64h,al
Маскировка немаскируемого прерывания NMI на AT:
	mov	al,0ADh
	out	70h,al
Разрешить NMI:
	mov	al,2Dh
	out	70h,al

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

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

Работа в защищенном режиме

Защищенный режим работы процессора открывает перед вами новую возможность. Возьмите любую программу, работающую в защищенном режиме, и попытайтесь запустить ее по управлением какого-либо отладчика (например, Turbo Debugger или Code View). Все будет хорошо до тех пор, пока ваша программа не попытается загрузить регистр IDTR при помощи команды LIDT. После выполнения этой команды отладчик "зависает" и единственное средство вновь оживить компьютер - нажать на кнопку сброса. Причина - изменились расположение и формат дескрипторной таблицы прерываний. Она подготовлена для работы в защищенном режиме, но отладчик работает в реальном режиме. Поэтому обработка всех прерываний, в том числе и от клавиатуры, невозможна. Идея использования защищенного режима работы процессора при создании программ, защищенных от несанкционированного доступа, копирования, очевидна.

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

Находясь в защищенном режиме вы не можете обращаться к функциям DOS и BIOS, - вы можете читать и писать секторы дискеты только используя уровень портов ввода/вывода контроллера флопи-диска.

Оглавление


© Колесников Дмитрий Геннадьевич Rambler's Top100 Учебник по СайтоСтроению