Лекции. Введение. Защита программного обеспечения от несанкционированного использования с помощью программноаппаратных средств
Скачать 4.72 Mb.
|
Глава 2. Методы противодействия обратному проектированию 2.1. Методы противодействия отладчикам Таким образом, задача злоумышленника во многом упрощается после локализации им модуля защиты в коде программы. Один из подходов, используемых разработчиками для защиты от взлома – не дать взломщику локализовать данный модуль или во многом затруднить ему эту задачу. Так как решение данной задачи во многом облегчается с помощью средств отладки, то многие разработчики ПО пытаются противостоять данным средствам и реализовать различные защиты против средств отладки. Различают два типа защит против отладочных средств – защиты против отладчиков реального режима и защита против отладчиков защищенного режима. Несмотря на то, что конкретные реализации данных типов защит, зачастую, значительно различаются, можно выделить несколько общих подходов, используемых как в первом, так и во втором типе. Данные подходы представлены ниже. 1. Использование триков (ловушек), с помощью которых можно выявить наличие отладчика в оперативной памяти, и, соответственно, прекратить работу, либо затруднить процесс отладки. 2. Определение наличия отладчика в оперативной памяти используя различные «дырки», допущенные при реализации отладчиков, либо внедренные разработчиком отладчика принудительно. Использование недокументированных команд и возможностей процессора. Использование того, что некоторые отладчики при загрузке отлаживаемой программы не могут полностью эмулировать «чистую» среду ее запуска в ОС (например, обнуляют некоторые регистры, которые могут нести определенный смысл). Рассмотрим более подробно реализации защит против отладчиков реального и защищенного режимов. 2.1.1. Защита от отладчиков реального режима Работа данных отладчиков построена на использовании двух аппаратных прерываний: int 1, с помощью которого в отладчике выполняется пошаговое исполнение программы и int 3, предназначенное для вставок точек останова в код отлаживаемой программы. При трассировке программы отладчиком задействуется регистр флагов, а именно флаг трассировки TF. Трик 1 Данная ловушка основана на том, что вследствие аппаратной особенности реализации процессора INTEL, после исполнения команды pop ss первое прерывание не может быть вызвано, и отладчик «не замечает» и пропускает следующую за данной командой инструкцию. Механизм защиты в данном случае может выглядеть следующим образом.
Трик 2 Перехват в программе прерывания 1 и использование его для ссылки на механизм защиты или на совершенно другую область. Работа данного механизма может заключаться в том, что при попытке отладки программы, она будет либо завешиваться, либо будет стартовать модуль защиты, который, например, будет выводить на экран сообщение о невозможности продолжения дальнейшей работы. Механизм защиты может выглядеть в данном случае следующим образом.
Существуют также механизмы противодействия установке точек останова в отладчике. Точка останова используются отладчиком следующим образом. В код программы вставляется вместо данной точки код 0xCC, а значение памяти, которое находилось по его адресу, запоминается отладчиком. Когда программа встречает команду с кодом 0xCC, она вызывает исключительную ситуацию 0x3h. При этом в стеке запоминается регистр флагов, указатель текущего кодового сегмента (CS), указатель команд IP, запрещаются прерывания (очищается флаг FI) и сбрасывается флаг трассировки. После выполнение действий п.2, программа поступает в монопольное распоряжение процессора. Обычно, при входе в отладчик, он сохраняет текущие значения всех регистров программы, а потом обращается к ним через стек, либо присваивает своим локальным переменным. Таким образом, установка точки останова требует непосредственной модификации кода программы (0xCC), вынуждая отладчик изменить соответствующий байт программы. Для отлаживаемой программы не составит труда обнаружить факт изменения своего кода и прекратить исполнение программы. Используя данный факт, для защиты от установки точек останова можно воспользоваться следующими методами. 1. Зашифровать код программы на некотором ключе, зависящем от контрольной суммы исполняемого кода. При изменении кода программы, расшифровка будет неверна и исполнение программы будет нарушено. 2. Установить из программы вершину стека в нуль. Так как стек ОС растет сверху вниз (к нижним адресам памяти), то когда процессор встретит точку прерывания, установленную отладчиком, то попытается в первую очередь сохранить регистр флагов в стеке, однако сделать этого не сможет, так как стек уже исчерпан. В данном случае ОС завершит некорректно работающее приложение с ошибкой. 3. Зашифровать код программы на ключе, который занести в стек, читая его по команде pop посимвольно. В данном случае, при вмешательстве отладчика код программы будет расшифрован неверно. Трик 3 Аппаратное запрещение прерываний Данная ловушка основана на том, что запрещение прерываний от клавиатуры приводит к зависанию отладчиков реального режима. Если войти в режим отладки, то невозможно будет набрать ни одну команду отладчика, в том числе и выйти из него. Запретить прерывания от клавиатуры в реальном режиме MS-DOS можно следующими способами. Способ 1 …… In al, 21h Or al, 00000010b Out 21h,al Способ 2 In al, 61 Or al, 10000000b Out 61h, al Способ 3 Mov al, 0Adh Out 64h,al Трик 4. Перепрограммирование видеоадаптера Многие отладчики реального режима перестают корректно работать, если запретить видеовывод через прерывание int 10h. 2.1.2. Защита от отладчиков защищенного режима Особенностью отладчиков защищенного режима является возможность их полной изоляции от выполняемой программы. В связи с этим, задача обнаружения отладчика в памяти стандартными средствами значительно усложняется. Кроме этого, особенностью защищенного режима является введение специализированных отладочных регистров DR0 – DR7, предназначенных для отладочных целей (таких как установка точек останова на обращение к определенным адресам памяти и портам). Отладчик защищенного режима запускается с нулевым уровнем привилегий CPL (Code Privelege Level), и полностью защищен от возможных воздействий со стороны отлаживаемой программы, которую он запускает с более низким уровнем привилегий. И взламываемая программа, при правильной реализации отладчика, уже не может «отравить» жизнь отладчику, "подпортив" регистры. Однако, в данном случае возможно определить наличие отладчика в оперативной памяти. При этом могут быть использованы следующие методы. 1. Один из методов основан на том, что при наличии отладчика в памяти, сама программа не может получить доступа к регистрам отладки – доступ к ним запрещен. Этот тип защиты можно отнести как к обнаружению отладчика в оперативной памяти, так и к невозможности полного эмулирования чистой среды загрузки программы.
Используя невозможность доступа к регистрам отладки, можно прибегать к разнообразным трикам. Трик 1. Шифрование кода программы с расшифровкой через отладочный регистр.
Трик 2. Основан на том, что отладчики защищенного режима теряют одно трассировочное прерывание при установке DRx. Это позволяет использовать трик, аналогичный трику 1 отладчиков реального режима (потеря трассировочного прерывания по команде pop ss). 2.1.3. Методы, основанные на невозможности полного эмулирования отладчиком среды загрузки программы Достаточно часто разработчики отладчиков также допускают ошибки или специально оставляют дырки в своих продуктах. Чаще всего, это вызовы API функций, которые могут быть доступны и отлаживаемой программе. Одной из таких дырок «страдает» наиболее распространенный отладчик защищенного режима SoftIce. Пример определения SoftIce из отлаживаемой программы.
Одной из уязвимостей отладчика Turbo Debuger является то, что он перехватывает прерывание int 00 (Деление на 0), выставляет свой адрес обработки, и передает управление на соответствующий код ядра отладчика. В данном случае, защита в программе может быть реализована следующим образом.
Многие отладчики, также обнуляют некоторые регистры, которые могут нести определенный смысл. Например, при нормальном запуске (из под операционной системы MS-DOS) AX и BX отражают правильность аргументов в командной строке. DS=DX, SI=IP, DI=SP. Равенство соответствующих регистров друг другу можно проверить. Отладчик SoftIce содержит также, одну ошибку, позволяющий его обнаружить. Эта ошибка заключается в неправильной установке регистра SP, указывающего на верхушку стека. При запуске отлаживаемой программы, SoftIce уставнавливает SP на 2 меньше, чем нужно. Ниже приведен пример, позволяющий, обнаружить SoftIce на основе данной ошибки (под SoftIce он не работает). …… Pop bx Pop eax Push eax Push bx Ret …… Ниже приведены дополнительные особенности, позволяющие выявить в памяти некоторые отладчики. 1. Отладчики Turbo Debugger, CodeView. Используют стек отлаживаемой программы. Используют int 1 и int 3 для трассировки. Перехват прерываний int 0,1,3 и FP инструкций. Некорректная работа с видеобуфером. Некорректное выставление начальных значений регистров. Неправильное дизассемблирование инструкций вида JMP $+1. 2. Отладчик SoftIce – обнаруживается по присутствию VxD устройства WinIce. Точку входа в SoftIce можно получить при вызове функции int 2F с параметрами: AX=1648h, BX=0202h (WinIce VxD ID), ES:DI=0. Результат возвращается в ES:DI. 3. Отладчик DegLucker Переключение в нестандартный видеорежим. Невозможность перехвата портов ввода/вывода. Запирание клавиатуры через i/o портов 60h/64h. Предоставляет API через int 15h функции 0FFxxh. Трассирует программу через DRx (аппаратные точки останова). Кроме этого, в программных продуктах могут быть использованы следующие средства, позволяющие затруднить их отладку. 1. Использование прерываний int 1 и int 3 для собственных нужд. 2. Неоднократная смена стека программы. 3. Переход на свои подпрограммы из обработчиков прерываний. 4. Передача информации через буфер клавиатуры. 5. Расшифровка программы через стек (хранение в нем пароля). Однако следует отметить, что рассмотренные методы не являются универсальными. Это всего лишь результат особенностей архитектуры процессора и инструментов отладки. Данные особенности ограничены, а их принципы исчислимы. Трики можно обойти, если злоумышленник знает о них. Если же злоумышленник не знаком с некоторым триком, то какие-то критические моменты, вызванные срабатыванием трика, могут его насторожить, и при повторной прогонке этого участка кода, взломщик может обойти это место без особого труда. Преимуществами защиты от отладки с помощью перечисленных методов является то, что данные методы, трики между собой не связаны, их можно очень легко применять. Недостатками данных методов является то, что они предназначены для защиты только от начинающих взломщиков, профессионалы их обойдут. Трики, как правило, включаются в программу по правилу «чем больше тем лучше» и от их исключения, логика работы программы не меняется. В некоторых случаях, совокупный размер кодов триков сравним с размером программы. 2.2. Методы противодействия дизассемблированию программного обеспечения Дизассемблирование машинного кода злоумышленником проблематично даже в том случае, когда противодействие ему не предусмотрено в связи с тем, что данные и код находятся в одном адресном пространстве. В связи с этим, задача автоматической идентификации какого-то участка программы как данных или как кода довольно сложна и неоднозначна: программист может использовать код как данные или наоборот. Выделяют несколько общих подходов к защите ПО от дизассемблирования. 1. Шифрование кода. Защищаемый участок кода шифруется каким-либо алгоритмом, а в программу добавляется модуль расшифровки, который в нужный момент расшифровывает его и передает ему управление. В данном случае, защищаемый участок кода перед дизассемблером предстанет в зашифрованном виде, и будет воспринят дизассемблером неверно. Перед злоумышленником в данном случае предстанет просто «мусор». Этот метод при неправильном использовании достаточно уязвим, ввиду того, что алгоритм расшифровки доступен взломщику, необходимо лишь найти его и произвести расшифровку. Поэтому, шифрование должно производиться на секретном ключе, который доступен только легальному пользователю и никому другому. Хранение ключа в программе или в каком-нибудь файле на диске сводит данную защиту на «нет». 2. Самомодификация кода программой. В данном случае, средства статического исследования ПО также не смогут верно воспринять участки кода, модифицируемые в процессе работы программы, ей самой. Следует отметить, что самомодифицироваться программы могут только при работе процессора реальном режиме (не в защищенном). 3. Различные ходы, приводящие к обману дизассемблера. Этот способ заключается в том, чтобы с помощью различных «хитрых» ходов запутать дизассемблер, подсунув данные вместо кода, или дизориентировать его логику, повести его по ложному следу. В качестве примеров такой защиты можно привести следующие участки кода. Пример 2.1. 00000000: B83534 Mov ax, 03435; “45” 00000000: EBFC jmps 00000001 ……дальнейший код. После выполнения данных инструкций, дальнейший код программы, воспринятый дизассемблером будет совершенно неверен., так как программа перейдет на инструкцию 3534EB, что соответствует команде xor ax, 0EB34h. На самом же деле программой выполняется следующий код. Mov ax,03435 Xor ax,0EB34 <- инструкция дизассемблером пропущена. …… дальнейший код. Пример 2.2.
Данная защита аналогична примеру 2.1. Существуют и чисто психологические способы защиты, которые сбивают с толку не дизассемблер, а человека, анализирующего текст программы после дизассемблирования. Например, обмен векторов прерываний 21h и 10h, или обращение к порту 378h как к 8378h. 4. Сокрытие команд передачи управления. Сокрытие команд передачи управления приводит к тому, что дизассемблер не может построить граф передачи управления. Например, можно модифицировать адреса переходов в ходе выполнения программы (только для реального режима). Пример 2.3.
Пример 2.4.
В данном случае, злоумышленник анализируя дизассемблированный текст, видит не действительные адреса переходов, а те, которые были до модификации кода. 5. Использование методики косвенной передачи управления также затрудняет анализ дизассемблированного кода. Mov bx,1234h Jmp dword ptr cs:[bx] 6. Использование нестандартных способов передачи управления. В данном случае, для затруднения изучения дизассемблированного кода, разработчиком ПО могут использоваться нестандартные способы передачи управления по каким либо адресам. Разработчик может, например, моделировать работу операторов JMP, CALL, INT … средствами других операторов, что затрудним понимание листинга на языке ассемблера. Примеры возможных альтернативных записей команд передачи управления приведены в таблице 2.1. Таблица 2.1. Примеры возможных альтернативных записей команд передачи управления
7. Использование ссылок вида [EAX+0ffffff64h] (в качестве эквивалента [EAX-9Ch]) с целью затруднения анализа дизассемблированных текстов человеком. 8. Можно сделать короткий переход вперед, а между командой перехода и адресом перехода встроить мусор. В данном случае, можно добиться совершенно неверного результата дизассемблирования. 2.3. Защита, основанная на человеческом факторе злоумышленника Как было показано выше, одна из целей разработчиков защиты ПО от отладки и дизассемблирования – как можно сильнее затруднить данный процесс, чтобы он отнимал у злоумышленника большое количество времени, либо отбить желание у злоумышленника исследовать программу под отладчиком и анализировать дизассемблированный текст. Одна из методологий осуществления этого – атака на человеческий фактор взломщика (взломщик атакует защиту, а разработчик защиты атакует взломщика). Данная атака заключается в атаке на психику и психологию взломщика, на то, чтобы как можно больше раздражать его исследованием кода программы. Ниже приведено несколько подходов к осуществлению такой атаки. 1. Перемешивание кода программы. В данном случае пишется специальный модуль, перемешивающий инструкции кода программы. Если первую инструкцию оставить на месте, другие разбросать в разные области, и перед каждой следующей инструкцией вставить команду перехода на нее, то общая последовательность выполнения инструкций программы не изменится и код будет иметь тот же семантический смысл. Пример 2.5. Пусть оригинальный код выглядит следующим образом: start: Инструкция 1 Инструкция 2 Инструкция 3 Инструкция 4 end: Тогда, данный код может быть, например, перемешан следующим образом: start: jmp @@1 @@4: instruction 4 jmp end @@2: instruction 2 jmp @@3 @@1: instruction 1 jmp @@2 @@3: instruction 3 jmp @@4 end: Допустим, что в тексте программы находится типичный кусок какой-либо классической защиты, например, 0000000: 16 push ss 0000001: 17 pop ss 0000002: 9C pushf 0000003: 58 pop ax 0000004: A90001 test ax 0000007: 740C je DebuggerDetected Суть происходящего в этом примере очевидна: вся последовательность команд у злоумышленника перед глазами и ничто не составляет для него труда проанализировать эту последовательность и обойти ее. Если же перемешать данный участок кода, то провести анализ кода будет уже гораздо сложнее: в отладчике злоумышленник будет наблюдать постоянные прыжки с места на место, причем отследить, откуда была сделана очередная команда перехода, будет весьма проблематично (не все отладчики имеют подобные функции). Если дизассемблировать такой исполняемый файл, то мы получим листинг, который придется постоянно листать взад - вперед, отслеживая выполнение программы. Перемешанный код может в результате выглядеть, например, следующим образом. 00000000: 16 push ss 00000001: EB07 jmps 00000000A 00000003: 58 pop ax 00000004: EB08 jmps 00000000E 00000006: 740B je RealModeDebuggerDetected 00000008: EB09 jmps 000000013 0000000A: 17 pop ss 0000000B: 9C pushf 0000000C: EBF5 jmps 000000003 0000000E: A96400 test ax,00064 00000011: EBF3 jmps 000000006 Если таким образом обработать достаточно большой участок кода, то разобраться в результатах обработки будет в практически невозможно. Данную обработку можно выполнять последовательно несколько раз. Для восстановления исходной последовательности команд придется писать раскодировщик результата перемешивания путем сохранения реальных инструкций без сохранения прыжков, однако, написание такого раскодировщика сопровождается рядом трудностей (например, собственно трудоемкость написания раскодировщика, трудность восстановления команд с относительной адресацией и т.д.). 2. Задача анализа кода взломщиком намного усложнится, если в некоторых случаях команду безусловного перехода заменить на команду условного (в данном случае, мы должны быть точно уверены в содержимом регистра флагов). Например, в следующем случае, переход будет осуществляться всегда: Xor ax,ax Je 000000025 |