Защита программ от отладки на платформе Windows


Макаров Тихон

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

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

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

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

Отладчики делятся на несколько типов:

Отладчики

К ним относится такая известная программа, как SoftIce. Они используют трассировочное прерывание Int 1, прерывание Int 3 и флаг прерывания TF.

а) Отладчики реального режима:

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

-) Первая возможность защиты основывается на устройстве процессоров Intel линейки 80х86: при изменении сегментных регистров, они "теряют" трассировку одной команды:

mov	 ax,ss
push	 ax
pop	 ss
pushf
pop	 ax
pushf
pop	 bx
sub	 ax,bx
mov	 bx, OFFSET Otladka
mov	 [bx],ax

-) Вторая возможность основана на проверке байта в векторе Int 3. В приведенном ниже листинге, мы прибавляем к 0 значение этого байта (адрес лучше всего брать не пользуясь прерыванием int 21h). Если отладчик запущен - байт не будет равен 0.

xor	ax,ax
mov	es,ax
mov	bx,0Ch
xor	dh,dh
mov	dl, BYTE PTR es:[bx]
mov	bx, OFFSET Otladka
mov	ax,[bx]
sub	dl, 0CFh
add	ax,dx
mov	[bx],ax

Теперь если где-то не 0 значение Otladka - вызываем функцию, обрабатывающую этот случай.

! -) Третья - многие отладчики после 41-ого прерывания, в АХ выдают код 0F386h (я не знаю почему, видимо это их секрет).

mov ax,4fh
int 41h
cmp ax, 0F386
jz Otladka

-) Четвертая - время выполнения программы. При пропускании команды через интерпретатор отладчика, время выполнения значительно увеличивается (в данном случае оно измеряется в тактах процессора).

rdtsc 
sub eax, ebx 
sbb edx, ecx 
rdtsc 
add eax, ebx 
adc edx, ecx 
rdtsc 
sub eax, ebx 
sbb edx, ecx 
rdtsc 
sub eax, ebx 
sbb edx, ecx 

в данном случае в EAX & EDX заносятся значения времени выполнения. Если в EDX получится 0 после выполнения этих функций - все в порядке, иначе - нет. Хочу заметить, что Intel указывает, что надо выполнять команду rdtsc (именно она узнает о прошедшем времени) 4 раза!

б) Защищенные отладчики:

-) Ищем handler отладчика по прерыванию Int 68:

mov ah,43h
int 68h
cmp ax,0F386h
jz Otladka

! -) Существует функция IsDebuggerPresent

BOOL IsDebuggerPresent(VOID) находится в Kernel32. Если наша программа находится под отладкой, то при вызове этой функции, она вернет 0, иначе - не 0.

-) Это способ отдельно для SoftIce.

За то, чтобы компании Numega позволили распространять свой продукт и "вживлять" в ядро windows XP, tq пришлось заплатить высокую цену - у нее постоянное имя в списке запушенных служб (на уровне 0, подробнее см. ниже), а также постоянный ID. В VxD он 202h.

С помощью команды CreateFile, как советует автор одной из статей того же рода, что и моя, или вызовом своего драйвера, мы можем определить наличие SoftIce. Для драйвера мы должны искать \\.\NTICE (в NT) и \\.\SICE (в 98). Если же надо узнать ее по handle в VxD, то выполняем нижеследующие операции:

mov     ebx,00000202h
VxDCall VMM_Get_DDB
xchg    ebx,ecx
jecxz   We_didnot_find_debugger
jmp     We_found_debugger

-) И еще один способ для защищенных отладчиков (хотя помогает и для некоторых реальных). Drx - регистры, в которых хранятся точки останова программы (хуки). Именно в них отладчики типа SoftIce заносят данные о перехвате. Нам достаточно занести в них свои значения и если хотя бы один изменится - мы под отладчиком.

Дизассемблеры

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

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

jmp	Proc
add	bx,7
dec	ax
int	21
DB	0EAh
...
Proc:
...

Еще лучше, если дизассемблер не поймет, что до какого-то перехода дело просто не дойдет:

mox	bx,0
mov	ax,bx
cmp	ax,0 	// условие ax=0 соблюдается всегда, но 
		// куда там глупому дизассемблеру это понять...
je	go
DB	0EAh

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

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

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

Обманщики и прочее.

Помимо всего этого хочу подсказать, какие проверки лучше всего выполнять периодически при выполнении программы:

1) Проверка контрольных сумм
2) Проверка времени выполнения программ
3) И еще - очень интересный прием - мы реализуем функции несколькими путями и будет выбирать их случайным образом. Согласитесь, что когда взломщик обезвредит все ловушки на своем пути, перезапустит программу, его будет ждать очень большое разочарование (а начинающего взломщика - смятение), когда он увидит совершенно другой код. Приведу небольшой пример подобной программы:

Pass	Macro	Reg
	local p1,p2,p3
push 	ax
call 	random
cmp	al,100
jn	pass2
pop	ax
sub	ax,19
jmps	ending
Pass2:
push 	ax
mov	dx,ax
sub	dx,20
dec	dx
pop	ax
mov	ax,dx
jmps	ending
Ending:
endm

Вот, простой пример, который прибавляет к ax 19 разными способами.

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

Защита программ от отладки на платформе Windows (ч. 2)