Неявный самоконтроль как средство создания не ломаемых защит [Крис Касперски] (pdf) читать постранично, страница - 3

Книга в формате pdf! Изображения и текст могут не отображаться!


 [Настройки текста]  [Cбросить фильтры]

преднамеренном нарушении стабильности работы самой защи
щенной программы.

П

Крис Касперски

5

Неявный самоконтроль как средство создания не ломаемых защит
Стоп! Ведь выше мы говорили как раз об обратном. Единственный путь сде
лать защиту трудно ломаемой, – не выдавая никаких ругательных сообщений, по
которым нас можно засечь, молчаливо делать "винегрет" из обрабатываемых дан
ных. А теперь, выясняется, что делать этого по этическим (и юридическим!) сооб
ражением нельзя. На самом деле, если хорошо подумать, то все эти ограничения
легко обойти. Что нам мешает оснастить защиту явной проверкой целостности
своего кода? Хакер найдет и нейтрализует ее без труда, но это и не страшно, по
скольку истинная защита находится совершенно в другом месте, а вся эта бутафо
рия нужна лишь затем, чтобы предотвратить последствия непредумышленного ис
кажения кода программы и поставить пользователя в известность, что все данные
нами гарантии (как явные, так и предполагаемые) ввиду нарушения целостности
оригинального кода, аннулируются. Правда, при обсуждении защиты данного ти
па, некоторые коллеги мне резонно возразили, а что, если в результате случайного
сбоя окажутся изменены и контролируемые ячейки, и сама контрольная сумма?
Защита сработает у легального пользователя!!! Ну что мне на это ответить? Случай
но таких "волшебных" искажений просто не бывает, их вероятность настолько
близка к нулю, что… К тому же, в случае срабатывания защиты мы ведь не форма
тируем легальному пользователю диск, а просто нарушаем нормальную работу про
граммы. Путь и предумышленно, все равно, если в результате того или иного сбоя
был искажен исполняемый файл, то о корректности его работы более говорить не
приходится. Ну хорошо, если вы так боитесь сбоев, можно встроить в защиту хоть
десяток явных проверок, – трудно нам что ли?!
Ладно, оставим этические проблемы на откуп тем самым пользователям, ко
торые приобретают титул "лицензионных" исключительно через крак, и перейдем
к чисто конкретным вещам. Простейший пример реализации данной защиты приве
ден в листинге 1. Для упрощения понимания и абстрагирования от всех технических
деталей, здесь используется простейшая схема аутентификации, "ломать" которую
совершенно необязательно: достаточно лишь подсмотреть оригинальный пароль,
хранящийся в защищенном файле прямым текстом. Для демонстрационного приме
ра такой прием с некоторой натяжкой допустим, но в реальной жизни, вам следует
быть более изощренными. По крайней мере следует добиться того, чтобы ваша защи
та не ломалась изменением одного единственного байта, поскольку в этом случае да
же неявный контроль будет легко выявить. Следует так же отметить, что контроли
ровать все критические байты защиты – не оченьто хорошая идея, т. к. хакер сможет
это легко обнаружить. Если защита требует для своего снятия хотя бы десяти моди
фикаций в различных местах, три из которых контролируются, то с вероятностью
~70% факт контроля не будет обнаружен. Действительно, среднестатистический ха
кер следить за всеми модифицированными им байтами просто не будет. Вместо это
го он, в надежде что тупая защита контролирует целостность своего кода целиком,
будет следить за обращениями к одной, ну максимум двумтрем, измененным им
ячейкам и… с удивлением обнаружит, что защита их вообще не контролирует.
После того, как контрольные точки выбраны, вы должны определить их сме
щение в откомпилированном файле. К сожалению, языки высокого уровня не по
зволяют определять адреса отдельных машинных инструкций и, если только вы не
пишите защиту на ассемблерных вставках, то у вас остается одинединственный
путь – воспользоваться каким ни будь дизассемблером (например, IDA).
Крис Касперски

6

Неявный самоконтроль как средство создания не ломаемых защит
Допустим, критическая часть защиты выглядит так и нам необходимо про
контролировать целостность условного оператора if, выделенного жирным синим
шрифтом:
int my_func()
{
if (check_user())
{
fprintf(stderr, "passwd ok\n");
}
else
{
fprintf(stderr, "wrong passwd\n");
exit(1);
}
return 0;
}

Загрузив откомпилированный файл в дизассемблер, мы получим следую
щий код (чтобы быстро узнать которая из всех процедур и есть my_func, опирайтесь
на тот факт, что большинство компиляторов располагает функции в памяти в по
рядке их объявления, т. е. my_func будет вторая по счету функция):
.text:00401060
.text:00401060
.text:00401065
.text:00401067
.text:00401069
.text:0040106E
.text:00401073
.text:00401078
.text:0040107B
.text:0040107D
.text:0040107E
.text:0040107E
.text:0040107E
.text:0040107E
.text:00401083
.text:00401088
.text:0040108D
.text:0040108F
.text:0040108F

sub_401060

proc near
; CODE XREF: sub_4010A0+AFvp
call
sub_401000
test
eax, eax
jz
short loc_40107E
push
offset aPasswdOk
; "passwd ok\n"
push
offset unk_407110
call
_fprintf
add
esp, 8
xor
eax, eax
retn
;