Теоретические основы крэкинга: Глава 2. Почти начинаем ломать. — Архив WASM.RU
Мы откроем сундук, хотя бы пришлось из-за него умереть...
Р.Л. Стивенсон, "Остров сокровищ"Итак, допустим, что у нас есть программа и в ней содержится вредоносный код (далее - "защита"), который нужно обезвредить. Инсталляционный файл тихо лежат на нашем винчестере, ожидая того знаменательного момента, когда мы его запустим, чтобы извлечь на свет скрытую в его недрах программу. Мне вполне понятно Ваше желание немедленно установить эту программу и вступить в битву со злобным и коварным врагом, но охладите на несколько минут свой пыл, и послушайте мой рассказ о процессе инсталляции программ, и чем этот процесс может кончиться.
Итак, многие программы в настоящее время поставляются в виде инсталляционного пакета. Для установки программы, как правило, требуется либо запустить один из файлов пакета (это, в частности, отлично всем известные Setup.exe), либо открыть при помощи другой заранее установленной программы (к примеру, файлы с расширением MSI, созданные Microsoft'овским инсталлятором или RPM-пакеты в Linux). В инсталляционных пакетах кроме самих файлов, которые требуется установить на машину пользователя, содержится также описание сценария инсталляции в том или ином виде (назовем это описание сценария для простоты "инсталляционным скриптом", тем более, что чаще всего так оно и есть). Разумеется, инсталлятор может быть и обычной программой, написанной для установки конкретного приложения, но написание собственного инсталлятора - дело достаточно трудоемкое, и поэтому на практике такие инсталляторы встречаются весьма редко.
Инсталляционный скрипт описывает, в каком режиме устанавливается тот или иной файл (добавление, замена, замена с предварительной проверкой версии и т.п.), какие данные необходимо внести в реестр или файлы конфигурации, какие программы запускаются до, в процессе и после инсталляции, а также многое другое. В это "многое другое" могут входить и такие безусловно интересные вещи, как проверка серийных номеров, создание записей в реестре, а также распаковка и запуск программ. О встроенных в инсталляционный скрипт серийных номерах мы поговорим позже. А пока попробуем поразмышлять о том, в чем может заключаться опасность неконтролируемого создания ключей в реестре или запуска каких-либо программ.
Если Вы взламываете какую-либо программу, оснащенную ограничением на время использования или число запусков, один из "корней зла" может гнездиться именно в инсталляционном скрипте. Представьте себе такую ситуацию: программа хранит дату первого запуска и/или какую-либо иную информацию, необходимую для проверки на истечение срока пробного использования, в реестре. Разумеется, информация закодирована, предприняты меры, чтобы защиту не обманывало "подкручивание" системной даты, возможно даже соответствующий ключ реестра хорошо замаскирован (Вам ничего не напоминает мое описание? Да это же ASProtect - ломаный-переломаный, но, как ни странно, все еще популярный). Тем не менее, одна лазейка все-таки осталась - если триальный ключ в реестре отсутствует, защита считает, что раньше программа не запускалась. Поэтому защиту можно обмануть, просто удалив из реестра лишние ключики. А теперь представьте, что триальный ключ создается в процессе инсталляции, и если он отсутствует, программа просто перестает запускаться. Если Вы ставите целью взлома ликвидацию триальных ограничений, Вам могут потребоваться весьма значительные усилия, чтобы отыскать этот маленький, но зловредный ключик в огромном реестре еще более огромной Windows. Как вам такая перспектива? Если встроенных средств инсталлятора оказывается недостаточно для создания триальной "метки", это можно сделать при помощи небольшого исполняемого файла, который распаковывается в процессе инсталляции, запускается, делает свое черное дело и сразу после этого удаляется. Упрощенный вариант этого приема выглядит как автоматический запуск приложения после инсталляции, чтобы защита смогла создать триальные "метки" на компьютере пользователя.
В качестве метки, на основе которой программа будет проверять ограничения по времени или числу запусков, также может использоваться какой-либо файл, создаваемый в процессе инсталляции, особенно если этот файл запрятан в глубинах одной из системных директорий (да-да, я знаю, что мусорить в системных директориях нехорошо; осталось лишь объяснить это некоторым авторам защит) и при этом еще активно используется программой. Я встречал защиту, основанную на подобном принципе: дата создания одной из DLL использовалась для отсчета времени с момента установки, а сама DLL лежала в системной директории Windows среди сотен себе подобных и динамически подгружалась основной программой.
Какие средства мы можем применить, чтобы обнаружить и обезвредить эти и другие подобные приемы? Наиболее радикальным средством, разумеется, является декомпиляция инсталляционного скрипта - в этом случае мы получаем практически полную информацию о том, что происходит в процессе установки программы, а в некоторых случаях даже можем повлиять на этот процесс, внеся исправления в инсталлятор. Разобрать инсталляционный скрипт "по косточкам" - задача не самая простая, да и не всегда это необходимо. Поэтому на практике чаще пользуются другим типовым приемом, позволяющим обнаружить произошедшие изменения. Этот прием заключается в использовании утилит мониторинга, делающих "снимки" системы (реестра, размеров и дат создания/модификации файлов) до и после установки и затем анализирующих различия между снимками. Подробный журнал изменений, выдаваемый такими программами, позволяет легко обнаружить подозрительные ключи и файлы, появившиеся в процессе инсталляции. Забегая вперед, скажу, что такие же "снимки" рекомендуется делать и при прохождении других критических периодов работы программы о которых мы поговорим в следующей главе. Установить факт запуска каких-либо программ во время инсталляции можно при помощи утилит, отслеживающих создание и завершение процессов.
Однако деятельность программ, запускаемых в процессе инсталляции, не ограничивается одним лишь созданием триальных ключей. Дело в том, что набор функций, поддерживаемых инсталляторами, ограничен и некоторые действия (например, проверку серийного номера с использованием достаточно сложного алгоритма) выполнить средствами инсталляционных скриптов не всегда возможно. Один из приемов, применяемых в этом случае - запуск исполняемого файла, который и выполняет все необходимые операции, а потом тем или иным образом возвращает результат проверки в инсталлятор - существуют защиты, в которых проверка серийного номера реализована именно так. В некоторых инсталляторах для этих же целей предусмотрен интерфейс, позволяющий использовать плагины (плагин в виде динамически загружаемой библиотеки также упрятываются внутрь инсталлятора, в нужный момент распаковываются во временную директорию и после использования удаляются). Такие исполняемые файлы и плагины, разумеется, невозможно модифицировать напрямую и чаще всего не удается извлечь из инсталлятора для дизассемблирования и изучения, т.к. они хранятся внутри инсталлятора в сжатом виде, а большинство коммерческих инсталляторов несовместимы по формату с обычными архиваторами. Если Вы хотите исследовать такой исполняемый файл, Вам почти наверняка потребуется снять с него дамп, чтобы получить материал для загрузки в дизассемблер. Сделать это совсем несложно - запустите инсталлятор под отладчиком и поставьте точки останова на все функции, связанные с загрузкой модулей и библиотек (для плагина) или создания процесса (для EXE). В Windows это будут LoadLibrary[Ex], LoadModule[Ex], CreateProcess или устаревший WinExec соответственно. Запоминаем, откуда инсталлятор пытается загрузить файл, и затем "замораживаем" работу инсталлятора непосредственно перед исполнением этой функции патчем
MySelf: jmp MySelf
либо манипуляциями с атрибутами соответствующего процесса и потока (например, применив к нему функцию SuspendThread). Затем копируем нужный нам файл в надежное место и делаем с ним все, что только придет нам в голову.
Если такой файл, запускаемый во время инсталляции, работает сколь-нибудь длительное время (достаточное, чтобы обнаружить факт появления нового процесса и записать данные в его адресное пространство), Вы даже можете создать memory patch, позволяющий модифицировать код этого файла в памяти. Приведу практический пример: одна из программ в процессе установки запрашивала пароль, который было необходимо ввести, чтобы продолжить инсталляцию. Путем экспериментов удалось установить, что инсталлятор создавал в директории для временных файлов исполняемый файл со случайным именем, запускал его и затем блокировал доступ к файлу даже на чтение, после чего собственно ввод пароля и его проверка осуществлялась уже средствами этой запущенной программы. Заставить программу "признать" любой пароль при помощи правки кода программы в SoftIce не составляло особой сложности, но вот изготовить "классический" патч, который позволил бы устанавливать эту программу, не прибегая к помощи отладчика, оказалось крайне затруднительно. Но, поскольку ввод пароля осуществлялся в стандартном окне Windows, это дало возможность подойти к проблеме с другой стороны. После недолгих размышлений выяснилось, что можно создать программу-launcher, выполняющую следующие действия:
- Ожидание появления окна с заданным заголовком.
- Определение по хэндлу этого окна идентификатора процесса, создавшего окно.
- Внесение изменений в адресное пространство этого процесса (проще говоря, исправление программы в памяти), после которых любой пароль признавался верным.
Одним из важнейших искусств для крэкера, несомненно, является изготовление или добыча серийных номеров. Как заполучить серийный номер для программы, которая без этого номера даже не инсталлируется? Варианты "втихаря списать с лицензионного компакта" или "шантажом и пытками вытянуть из зарегистрированного пользователя", мы рассматриваем как не имеющие ничего общего с высоким искусством крэкинга, и потому изучать их не будем. Серийные номера вообще - это очень обширная тема, и в данном разделе я буду рассматривать только те серийные номера, которые "упрятаны" в инсталлятор. Несколькими строками выше я уже говорил о том, как обращаться с проверщиками серийных номеров, запускаемыми в процессе инсталляции. Теперь посмотрим, как можно решить проблему серийного номера, упрятанного в инсталляционный скрипт.
Наиболее удобным для нас был бы вариант серийного номера, записанного открытым текстом, но так уже практически никто не делает, поскольку "взломать" такую защиту можно при помощи обычного просмотрщика текстов. Так что будем считать, что при проверке правильности используется представление серийного номера как результата некой хэш-функции.
Часто в инсталлятор "зашит" не один серийный номер, а несколько - и это способно существенно облегчить нам задачу. Существует три метода проверки серийного номера:
- Вычислить хэш-функцию от серийного номера и проверить, удовлетворяет ли результат какому-либо условию.
- Пропустить результат через хэш-функцию и сравнить его со списком эталонов.
- Пропустить зашифрованные серийные номера, спрятанные внутри программы, через процедуру расшифровки и затем сравнить результат с значением серийного номера, который ввел пользователь. Очевидно, что это наиболее простой случай: требуется только обнаружить точку, в которой происходит сравнение введенного серийного номера с правильным и прочитать из памяти действительный серийный номер.
Различие между п.1 и п.2 не очевидно, но оно есть: именно на втором методе обычно основаны всевозможные "черные списки" серийных номеров. Первый способ проверки в инсталляторах применяется сравнительно редко из-за слабых математических возможностей интерпретаторов инсталляционных скриптов и ориентации на максимальную простоту процесса создания инсталляции (грамотная установка защиты, к нашему счастью, достаточно сложна и при этом все равно не дает гарантированного результата). Так что в итоге внутри большинства инсталляторов упрятан все тот же список серийных номеров (возможно, лишь из одного элемента). Еще интересно отметить, что абсолютное большинство инсталляторов никак не упаковывают инсталляционные скрипты (хотя исходный текст скрипта вполне может быть откомпилирован в байт-код), поэтому Вы можете сравнительно легко модифицировать эти скрипты при помощи шестнадцатиричного редактора.
Первое, что приходит в голову - найти, где в инсталляторе спрятан этот самый список и добавить туда свой серийник. Для этого нужно ввести какой-нибудь серийный номер, потом найти код, сравнивающий значение хэш-функции от введенного серийника со списком и вписать свое значение хэш-функции в файл инсталляции на место оригинального. Что называется, просто и со вкусом.
Другой способ заключается в том, чтобы идентифицировать программный продукт, при помощи которого сделана инсталляция, затем раздобыть этот продукт и как следует его проанализировать. Это позволит Вам сравнительно быстро узнать технические возможности инсталлятора (и проблемы, которые эти возможности могут Вам создать), структуру инсталляционного скрипта, способ генерации и формат хранения правильных серийных номеров, коды и функции внутренних команд интерпретатора скриптов. При желании Вы даже сможете написать распаковщик инсталляционных пакетов, декомпилятор инсталляций а то и универсальный(!!!) генератор ключей ко всем программам, инсталляционные пакеты которых сделаны при помощи исследованного Вами программного продукта.
В некоторых случаях наиболее прямым путем к получению полнофункциональной программы из урезанного варианта является именно вскрытие инсталлятора. Поясню эту идею примером. Допустим, что у Вас есть демо-версия какой-либо хорошей программы, но Вам этого мало, и Вы хотите иметь полностью функциональный вариант этого продукта. При этом у Вас нет никакого желания вручную восстанавливать недостающий код, заботливо выдранный автором. Однако на сайте производителя иногда можно найти обновления для коммерческих версий нужного Вам софта, и эти обновления, как правило, содержат исправленные варианты основных файлов программы. Что из этого следует? А то, что если неким таинственным образом Вам удастся установить апдейт на демо-версию, есть ненулевая вероятность, что свою демонстрационную версию Вы превратите в программу, по функциональности практически не отличающуюся от полной! Разумеется, программа обновления перед своей установкой выполнит проверку на возможность обновления (в том числе и для того, чтобы пользователь случайно не "обновил" демо-версию) - но ведь Вы решили изучать крэкинг именно для решения проблем такого рода. Так что Вам потребуется лишь немного "доработать" инсталлятор, ликвидировав в нем проверку на возможность обновления.
По сути, любой инсталлятор представляет собой самораспаковывающийся архив с достаточно сложным SFX-модулем. Более того, некоторые инсталляционные пакеты даже можно открыть обычными архиваторами! Прямым следствием этого является возможность распаковать содержимое инсталляционного пакета (если, конечно, оно не зашифровано). Даже если ни один из стандартных архиваторов "не берет" инсталляционный пакет, распаковать файлы можно вручную. Дело в том, что инсталлятор обязательно содержит внутри себя процедуру распаковки, и эта процедуру можно обнаружить и проанализировать. Вам понадобится узнать, где находится процедура распаковки, какие параметры принимает, и что эти параметры обозначают (хотя бы приблизительно). Если Вам это удастся, Вы сможете попытаться принудительно вызвать эту процедуру с нужными Вам параметрами, манипулируя кодом программы и состоянием регистров, и извлечь файлы из пакета. Задача поиска этой процедуры облегчается тем, что в инсталляционных скриптах указание на место, куда будут устанавливаться файлы, хранится в виде текста и, поставив точку останова на начала этих текстовых строк, Вы сможете обнаружить, откуда происходит обращение к этим строкам. Вообще, даже простой анализ текстовых строк, содержащихся в инсталляционном пакете, способен дать множество полезной информации. А теперь представьте себе программу, которая при установке просит некоторое условие, и, если это условие не выполнено, "забывает" установить пару-тройку файлов или устанавливает вместо нормальных версий этих файлов урезанные. Вот тут-то и Вам и пригодится возможность заглянуть внутрь архива и извлечь из него нужные файлы. © CyberManiac
Теоретические основы крэкинга: Глава 2. Почти начинаем ломать.
Дата публикации 13 янв 2004