- •Захист виконуванихфайлів від дослідження програмного коду
- •1 Теоретические сведения
- •1.1 Средства исследования программного кода
- •1.2 Защитные трюки
- •1.3 «Изощренное» программирование
- •Приложение а
- •2 Задание к выполнению работы
- •3 Содержание отчета
- •4 Контрольные вопросы
- •Рекомендованная литература
1.3 «Изощренное» программирование
Приемы защиты программ от отладчиков и дизассемблеров для программирования задач ответственного целевого назначения, конечно, хороши. Но, как уже отмечалось, абсолютно надежных защит не бывает, и поскольку задача разработчика защиты - вынудить противника потратить на взлом время, достаточное для принятия контрмер, только противоотладочными трюками ограничиваться не нужно. Нелишне также затруднить исследование программы и после того, как противник обойдет или устранит все ловушки для хакерского инструментария.
Для этого необходимо сделать программу высокочитабельной для нас, но малопонятной в дизассемблированном виде, а под отладчиком - создающей впечатление хаотичного набора условных и безусловных переходов. Эта задача решается применением так называемого "изощренного" программирования.
Можно выделить несколько основных направлений:
экзотическая, имеющая необычный вид реализация алгоритмов с использованием редких команд процессора или их нестандартных сочетаний;
реализация нескольких полностью эквивалентных вариантов одного и того же алгоритма, при каждом обращении к которому случайным образом выбирается один из вариантов его реализации;
засорение кода "мусором" - командами, не влияющими на обработку наших данных (кроме некоторого увеличения времени обработки на "засоренных" участках).
Рассмотрим эти приемы подробнее.
Экзотическая реализация алгоритмов. Допустим, у нас есть некоторый флаг (или переменная), для которого критически важна проверка на 0. Однако мы не желаем явно писать команду СМР АХ, 0 и вообще по возможности хотим обойтись без команд передачи управления.
Первое, что приходит в голову - использовать команды, пригодные для неявной проверки на 0. Например, использовать команды двоично-десятичной арифметики.
mov ах, OurFlag
daa
pushf
pop ax ; Неявная проверка флага нуля
and ах, 40h
jz FlaglsZero
Разумеется, в реальной программе получение флагов и проверка наличия флага нуля должны быть разнесены для все того же затруднения исследования. Однако этот вариант, хотя и применимый, относительно прост для взлома.
Взлом значительно усложняется, если мы установим обработчик нулевого значения флага на INT 0 (деление на 0), а проверку реализуем как деление чего-нибудь на значение флага, загруженное в любой допустимый регистр. В этом случае никакого перехода на обработчик нулевого значения флага нет вообще, а кроме того, некоторые отладчики аварийно завершают программу при выполнении деления на 0.
Реализация эквивалентных ветвей. Для затруднения исследования программы под отладчиком польза от этого приема очевидна. В самом деле, если мы, находясь в отладчике, попадаем то на одну команду, то на несколько, это явно не упростит понимание алгоритма программы.
Приведем простой пример. Операция NEG (преобразование числа в дополнительный
код) эквивалентна исключающему ИЛИ со "всеми единицами" и инкременту результата
Напишем макрос, реализующий алгоритм с двумя возможными ветвями его выполнения.
XNEG16 MACRO Reg
Local XNEG2, XNEGQ
push ax ;Используется
;процедурой Random
call Random ;Некоторая
;подпрограмма
;генерации
;случайных или
;псевдослучайных
;чисел
cmp al, 30
ja XNEG2
pop ax
neg Reg
jmps XNEGQ
XNEG2:
xor Reg, 0FFFFh
inc Reg
XNEGQ:
ENDM
Это лишь простейший пример, относительно легко поддающийся анализу. Для затруднения анализа возможно:
увеличить число ветвей (реализуется не для всех алгоритмов);
реализовать ветви в виде обработчиков прерываний (INT 1, INT 3, INT 4, INT6 и т.д.) и обращаться к ним не напрямую, а путем создания соответствующих ситуаций (взведением флага TF, засылкой команды INT 3 на место заранее поставленного NOP, делением на 0 и пр.), что, кстати, еще и расширит противоотладочную подсистему;
увеличить число проверок случайного числа.
Засорение кода. Под засорением кода будем понимать искусственное внесение в него команд, не имеющих отношения к реализуемому алгоритму, которое либо затрудняет его анализ, либо делает этот анализ более канительным, более утомительным и, следовательно, требующим больше сил и времени.
Речь идет о манипуляции незадействованными регистрами; установке/сбросе некоторых флагов, выполнении нескольких команд, на эти флаги не влияющих, с последующ, условным переходом, который на самом деле заведомо выполнится или заведомо не вы полнится; обработке нескольких одинаковых структур, из которых только одна содержит данные нашего алгоритма и т.д. Все это не только увеличивает объем дизассемблированного текста, но и отвлекает внимание от защищаемого алгоритма.
Полезно засорять код еще и макросами или вызовами, эмулирующими ввод с клавиатуры команд Soft-Ice, TR или комбинаций клавиш распространенных отладчиков, или даже просто нажатие некоторых случайных клавиш. Очевидно, при этом нужно почистить буфер перед вызовом любой подпрограммы обращения к клавиатуре.
