Эта статья была написана специально для преподавателя, следовательно автор к этому оносился небрежно, и не всегда добросовестно.
Любую программу можно взломать. Как говорил один известный в узком кругу человек - ORC "If it runs, it can be defeated" - "Если программа запускается ее можно взломать".
В основном, защиты можно разделить на два класса - когда защитные процедуры внедряются в программы на этапе ее разработки и навесные защиты - когда программа упаковывается в т.н. "конверт" - специальную защитную программу.
Еще встречается комбинированный метод - когда в самой программе есть какие то защитные процедуры и для того что бы было сложнее залезть внутрь применяют внешние программы. Практически всегда в этих программах присутствуют антиотладочные приемы, иногда они так же упаковывают защищаемую программу.
Таких программ великое множество: MegaShield, Anti-Lame, Pcrypt, Trap, Gardian Angel, SCRAM!, LockProg, ExeLock, USCC, MSCC, Fds-cp, AdFlt2, XiCOD, UnPackStop, HackStop, CrackStop, ProtEXE, Mask, Exeguard, Mess, XcomOR, Scrypt!, CnP, FFSE, $pirit, AsPack, AsProtect, Petite, Shrinker, ...
В зависимости от типа защиты есть различные методы взлома защищенных программ.
Программы с комбинированной защитой удобнее перед изучением распаковать для того, что бы можно было внести в программу необходимые изменения. Если распаковка по каким либо причинам невозможна можно воспользоваться специальной программой - run-time patcher'ом - в защищенной программе с помощью отладчика находится участок, который необходимо подкорректировать, найденные данные вносятся в патчер, потом с его помощью запускается исходная программа и патчер исправляет в запустившейся и распакованной программе нужные байты.
Некоторые методы взлома защит.
Любую систему защиты можно вскрыть за конечное время - это следует из того, что ее команды однозначно интерпретируются процессором. При этом время, необходимое для вскрытия хорошей системы защиты, оказывается сравнимым с написанием защищенной программы заново. Однако не все программы пишут профессионалы, поэтому часто можно вскрыть или обойти защиту, найдя ее слабейшее звено, сократив время вскрытия на несколько порядков. Для этого, в частности, можно рекомендовать следующие достаточно универсальные методы:
- если программа защищена только от средств статического анализа, она легко изучается динамически, и наоборот;
- "метод изменения одного байта" - в момент, когда система защиты сравнивает контрольную информацию (состояние операционной среды, контрольную сумму) с эталонной, простым изменением команды перехода она направляется по правильному пути;
- аналогично, результат работы функции, возвращающей текущую контрольную информацию, может быть подменен на эталонное (ожидаемое) значение (например, с помощью перехвата соотвествующего прерывания);
- в программах с защитой требующей ввести имя и соответствующий ему код можно разобраться в алгоритме генерации этого кода и написать программу которая будет по введенному имени генерировать правильный код. Также такой подход можно применять в программах с привязкой к компьютеру, когда защищенная программа по каким либо его параметрам (дата БИОС, серийный номер жесткого диска, его модель, модель материнской платы и процессора, ...) генерирует код и просит пользователя ввести ответный код.
- когда система защиты расшифровала критичный код, он может быть скопирован в другое место памяти или на диск в момент или вскоре после передачи управления на него. Частный случай - после окончания работы программы весь ее код расшифрован и доступен.
Естественно, что в профессиональных системах защиты все эти приемы не проходят, и потребуются многие дни или месяцы кропотливой работы в отладчике, чтобы разобраться в их функционировании. Для исследования таких систем необходимы специальные средства (естественно, их с успехом можно применять и для любых других программ). Можно предложить следующие перспективные направления:
Интерпретация программы в полностью виртуальной среде, где эмулируются процессор, память, внешние устройства, операционная среда и т.д. В этом случае все описанные приемы противодействия оказываются неэффективными; однако, какой бы прозрачной не была эмуляция среды, все равно она отличается от истинной, и программа может это распознать со всеми вытекающими отсюда последствиями. Самым трудным и слабым местом здесь будет точная эмуляция временных характеристик аппаратуры, недокументированных прерываний и т.п.
Также можно отметить еще одну проблему, стоящую перед злоумышленником при исследовании защиты от стандартных отладочных средств является отслеживание прерываний, перехватываемых исследуемой программой. Суть проблемы заключается в следующем. Практически все стандартные отладчики для обеспечения своей нормальной работы "забирают", как минимум, 1-е и 3-е прерывания. 1-е прерывание, называемое также трассировочным, используется для осуществления пошагового режима работы отладчика. 3-е прерывание необходимо для установки меток останова программы по адресам, определяемым пользователем. Защитный механизм программы обязательно должен перехватывать указанные прерывания для предотвращения беспрепятственного анализа защищенной программы под отладчиком. Следовательно, очередной задачей злоумышленника на данном этапе является отслеживание момента перехвата. Если эта задача решена, то далее он может продвигаться по одному из следующих 2-х путей. Во-первых, можно запретить перехват прерываний путем обхода данного участка программы. В этом есть доля риска, так как подпрограммы обработки соответствующих прерываний могут выполнять некоторые "полезные" функции, необходимые для нормальной работы программы. Если же ваш номер не прошел, то можно пойти по 2-му пути. Hеобязательно запрещать перехват прерываний. Если ваша квалификация достаточно высока и защитный механизм программы позволяет это сделать, то можно изменить подпрограммы обработки прерываний таким образом, чтобы после отработки своих функций они не сразу возвращали бы управление в основную программу, а передавали бы его соответствующим подпрограммам отладчика.
Отключение или иной "взлом" модуля проверки.
Модификация кода модуля защиты с целью обойти проверку или исказить ее результаты - один из наиболее распространенных методов, дающий самые удобные в использовании программы. Hо он также и является самым трудоемким и длительным.
Моделирование обращений к ключевой дискете.
Если не удается скопировать ключевую дискету, можно попробовать имитировать необходимый формат с помощью специальной программы, возвращающей в программу защиты все те коды завершения и ошибки, как при нормальной работе. Чаще всего такой метод применяется когда защита проверяет наличие какого-либо повреждения на поверхности дискеты ("дырки"), причем делает это через функции BIOS/INT 13h. В этом случае весь процесс тоже длится не более нескольких минут. Хотя даже обращения к контроллеру гибкого диска через порты можно отследить, отлаживая программу в защищенном режиме. Так же (или почти также) можно имитировать обращения к электронному ключу на LPT или COM порту.
Снятие программы из памяти и методы защиты.
Последнее время появилось множество программ снимающих навесные защиты методом снятия из памяти (Cup386, Intruder, IceDump, ProcDump). Принцип здесь очень прост. После того как защита отработала и дала "добро" на выполнение, в памяти находится исходная программа в таком же виде, как будто ее запустили обычным образом, без всяких защит. Если в этот момент содержимое ОЗУ записать на диск, то из получившегося dump'а можно извлечь первоначальную программу. В лучшем случае получается работоспособный EXE-файл, практически идентичный первоначальной программе (правда для этого процесс получения dump'а нужно будет повторить два раза, для нахождения элементов таблицы перемещений -- Relocation Table).
Хотя иногда удобнее подойти к этим программам так же как к программам с встроенной защитой.
Для распространенных типов навесных защит существуют универсальные программы для их снятия.
Почти каждая программа вызывает функцию завершения. Если ее перехватить, то высока вероятность, что программа в момент завершения в памяти не затерта, т.е. можно проанализировать ее систему защиты и/или саму программу.
Эмулирующие отладчики и виртуальные машины.
В дополнение к обычным отладчикам существует особый класс программ, называемых эмулирующие отладчики. Они не пытаются корректно выполнять трассировку работающей программы, которая, к тому же, активно этому сопротивляется, а сами интерпретируют и выполняют ее машинные инструкции (например, вместо MOV AX, 56 они присваивают переменной, соответствующей регистру AX - скажем, Reg_AX - число 56). Существуют также отладчики с неполной эмуляцией, которые эмулируют только "опасные" команды, а остальные выполняют на реальном процессоре.
Такие отладчики нейтрализуют практически все методы противодействия отладке: блокировку прерываний и устройств, работу с контроллерами через порты, подсчеты контрольных сумм для выявления контрольных точек, контроль стека, а также, методы основанные на особенностях процессора и DOS. Плюс ко всему, эти отладчики имеют просто фантастические возможности для отладки: можно поставить контрольную точку по записи/чтению/или чему угодно на любую область памяти или ряд портов ввода/вывода, и т. д. и т. п.
Все средства взлома и изучения программного кода можно разделить на три класса:
Отладчик - динамическое средство исследования (SoftIce, TD);
Дизассемблер - средство статического исследования (IDA, WinDasm, Sourcer, hiew);
Эмулирующий отладчик - интерпретирует и выполняет машинные инструкции (TRW, Cup386).
Для защиты от этих средств используются различные методы, и большинство из них базируется том, что программа может модифицировать саму себя. Этого бывает достаточно для подавления средств статического анализа. В случае защиты от динамических средств может быть использован тот факт, что изучаемая программа может это распознать его присутствие.
Методы защиты от отладчика.
Подавление изменения операционной среды - программа либо сама еще раз перенастраивает среду, либо вообще не может работать в измененной среде.
Противодействие установке контрольных точек - отладчик или не может установить контрольную точку, или программа распознает ее.
Hарушение интерфейса с пользователем приводит к тому, что пользователь не может пронаблюдать за ходом выполнения программы.
Использование "пустышек".
Переход на свои подпрограммы можно осуществлять из обработчиков прерываний. Запрещение прерываний от клавиатуры приводит к зависанию отладчиков реального режима.
Периодически следует проверять контрольные суммы отдельных участков программы, с целью избежать их изменения, например, установки контрольных точек.
Использование переназначения прерываний: обмен содержимого векторов int 21h и int 10h. Отладку не предотвращает, но сильно затрудняет.
Расшифровку защищенной программы через int 08h/int 1Ch. Отладку не предотвращает, но сильно затрудняет.
Изменение DRx регистров приводит к печальным результатам для отладчиков.
Контроль времени может использоваться с целью (причем замерять лучше не абсолютное время исполнения, а относительное).
Передача информации через буфер клавиатуры затрудняет отладку.
Проверка действительного запрещения аппаратных прерываний (прямое перепрограммирование контроллера прерываний).
Проверка перехваченных векторов на какое либо значение.
Предлагается проходить участок кода с поднятым флагом трассировки. При этом обработчик int 1h должен постоянно изменять этот код исходя из значений векторов, контрольных сумм участков программы, и т.д. Кроме того, вместе с этим может производиться расшифровка частей программы, опять же исходя из контрольных сумм. Хотя бы простейший контроль времени должен помешать полной трассировке этого "безумного кода" вручную.
Методы защиты от дизассемблера.
Hеважно, насколько мощной Вы сделали защиту от отладчика, но эта защита ничего не значит при использовании дизассемблера. Вполне объяснимо желание подавляющего большинства программистов работать с твердой копией исследуемой программы. Вследствие этого первоочередной задачей злоумышленника при взломе практически любой защиты является дизассемблирование исполняемого кода программы и получение листинга с мнемоническим изображением ассемблерных команд.
Для защиты от дизассемблеров приходится использовать специальные методы, которые приведены ниже:
Модификация кода программы приводит к тому, что дизассемблер не может достоверно распознать инструкции и/или данные:
- зашифровка критичного кода и расшифровка его самой системой защиты перед передачей управления на него, таким образом, дешифровка программы происходит не сразу, а частями и защита от дизассемблера оказывается распределенной по времени. При этом никогда не осуществляйте дешифрацию одной подпрограммой, т.к. ее будет легко вычислить и отключить. Также следует затирать те участки программы, которые уже не понадобятся. Шифрование исполняемого кода программы с целью защиты от дизассемблера является наиболее простым средством как в смысле его реализации, так и в смысле снятия. Шифрование может быть использовано лишь как часть защиты от дизассемблера и поэтому необязательно должно быть сложным;
- модификация кода непосредственно самой программой.
Скрытие команд передачи управления приводит к тому, что дизассемблер не может построить граф передачи управления:
- косвенная передача управления;
- модификация адреса перехода в коде программы.
Перекрывающийся код.
Также дизассемблер сбивает нестандартный формат загружаемого модуля (например, перекрыть весь сегмент кода стеком).
Переустановка векторов (int 21h -> int 60h). В итоге в программе не будет ни одного int 21h.
Можно использовать программу защиты в качестве ключа для расшифровки основного тела программы.
Программа может использовать для обращения к одной и той же области памяти разные сегментные адреса.
Записывается адрес процедуры расшифровки в какой-либо вектор. При необходимости вызывается int <# вектора>.
Все эти приемы защиты полезны против дизассемблеров которые работают без взаимодействия с пользователем - например Sourcer. В дизассемблере IDA пользователь может в процессе дизассемблирования указать программе что и как делать. Также в нее встроен Си-подобный язык на котором можно писать скрипты для расшифровки защищенных программ.