Охота на скринсейверы. Ломаем скринсейверы с 3d-нагамиСергей «_A1!3N» Аляев Мало вещей на свете столь же благоприятно сказываются на психике простого юзера, как поиск и установка красивого скринсейвера. Уверен, у тебя есть, по крайней мере, один юзер, психологическое здоровье которого тебя волнует и ради которого ты отправляешься в интернет-джунгли на поиски гламурного софта. В статье я расскажу о самых простых и быстрых способах крякнуть скринсейвер. Чтобы получить моральное удовольствие от скринсейвера, достаточно удалить НАГ (такой «квадрат Малевича», на котором написано про регистрацию). Некоторые приемы борьбы с НАГами в обычных Win-приложениях я описал в майском номере ][, в статье «Ты – повелитель армии лоадеров!». К сожалению, в случае 3D-приложений эти методы не подходят. НАГ-скрин в 3D-программе вовсе не то же самое, что НАГ в Win-приложении. В 3D НАГ нельзя послать сообщение, так как это не объект Windows (то есть, не окно) и манипулировать им извне стандартными методами WinApi не получится. А значит, SendMessage нам не поможет. 3D НАГ – это полноправный участник 3D-сцены, как пенек, цветок или мужик с автоматом, и удалить его оттуда можно только двумя способами. Первый – работа с частью кода проверки серийника и, как результат, написание кейгена или модификация кода, после которых подопытная программа становится зарегистрированной. Второй способ – работа с частью кода, отвечающего за генерацию признаков незарегистрированного софта (отсутствующие пункты меню, НАГ-скрин и т.д.). Соответственно, решение лежит на поверхности - вырезать этот самый код. Новичкам советую для начала выбрать второй путь, иначе есть риск заработать лысину и чувство собственной неполноценности. Видишь ли, криптография – это наука и с наскока проблему разных MD5, RSA не решить, а именно так и будет защищена (по закону подлости) твоя первая программа. Поэтому переходи на первый путь, только после того как протопчешь широкую тропу на втором. Однако не забывай и почитывать скучные (без картинок, но с формулами) мануалы по криптографии. Работать будем, как настоящие «джедаи», в основном с дизассемблером. Отладчики user mode (OllyDbg, MS Dbg) не особо удобны для отладки в связи со спецификой полноэкранных 3D прог. Рекогносцировка на местностиОпределимся для начала, какие признаки НАГа будем искать в коде. С точки зрения 3D-программиста, НАГ – это 3D-модель, состоящая или из двух треугольников, или одного полигона (OpenGL), или одного квадрата (OpenGL) с включенным альфа каналом для прозрачности и натянутой на него текстурой. Есть еще маленький нюанс: чтобы прозрачный НАГ корректно выводился, его надо визуализировать последним (надо отметить, что к вышеперечисленным признакам подойдет код, имитирующий снег, дым, пыль, дождь, огонь) и предустановленной ортогональной проекцией. Исходя из этого, определяемся с признаками присутствия кода НАГа:
Итак, начнем. Заходим в любой поисковик, вбиваем в него что-то подобное «Screensaver download» и качаем только красивые скринсейверы! Процесс исследования защит должен приносить эстетическое удовольствие :). Теперь устанавливаем, заходим в системную папку Windows и в ней же, в System32, копируем нужные *.scr в папку, где будем производить над ними эксперименты и... творить чудеса! Родной OpenGLНа роль первого клиента мне попался ElectriCalm 3D Screensaver. Откроем его в IDA (если кто не знает, дизассемблер такой, можно воспользоваться другим, но лучше не надо). На все ее вопросы ответим «да». Далее тип файла выбери PE Executable (*.scr – это тот же *.exe). Подожди, пока он сделает свое дело, и открывай окно Imports. Большое количество функций с префиксом «gl_» подтверждает, что скринсейвер использует OpenGL. Кликаем два раза на glOrtho. В таблице импорта напротив имени функции после DATA XREF: установлен адрес функции плюс смещение, откуда вызывается glOrtho. Кликни по нему.
Отлично, попадаем в маленькую функцию (которую IDA обозвала sub_405350) по соответствующему адресу 0x405350. Что она делает? Ничего особенного – является оберткой glOrtho, поэтому выделяем имя функции, жмем N и в появившемся диалоге Rename переименовываем функцию в Call_Ortho. Правее заголовка функции располагаются таинственные письмена под заголовком CODE XREF: как ты, наверное, догадался, это ссылки на адреса, откуда вызывается Call_Ortho.
Кликаем на первой из них sub_403C10+E02 и попадаем в весьма интересное место:
Перед нами типичный код, который собрался вывести перед носом прозрачный четырехугольник. Забегая вперед, скажу, что НАГ можно отключать уже сейчас путем модификации всего одной команды, но для чистоты научного эксперимента продолжим исследование кода. Отключение текстурирования (glDisable(GL_TEXTURE_2D)) вызывает некоторое беспокойство по поводу правильности выбора направления нашего расследования, ну да не будем пока обращать на это внимание. Попробуем заменить push 7 по адресу 0x00404A17 на push 0, что соответствует glBegin(GL_POINTS). Это заставит OpenGL выводить не квадраты, а точки. Задача glBegin – предварять список данных вершин и определять тип выводимых примитивов. Это не единственный способ рисования в OpenGL, но для вывода НАГа обычно пользуются им. Для модификации кода можно использовать OllyDbg, но я обычно пользуюсь Hiew. Открываем в Hiew файл ElectriCalm 3D Screensaver.scr и нажимаем <Enter> до тех пор, пока не попадем в режим дизассемблера. Жмем <F5> и в поле ввода вводим нужный адрес 0x00404A17, не забыв перед ним поставить точку, означающую, что это виртуальный адрес PE-файла, а не смещение относительно начало файла. Далее – <Enter>. Сейчас курсор находится на двоичном представлении(6A 07) асм-кода push 7. Давим на <F3>, редактируем машинный код в 6A 00. Затем <F9> для сохранения изменений. Запускаем скринсейвер - фон НАГа исчез (точнее, не исчез, просто OpenGL нарисовала не квадрат, а четыре очень маленькие точки), но НАГ-письмена остались. Теперь понятно, почему отключалось текстурирование! Текст в НАГе – это не картинка-текстура, а множество примитивов квадратов, на которые натягиваются соответствующие картинки-буквы. Все это мне подсказали внутренности функции sub_405110 (которую можно смело обозвать PrintStringLine), находящейся чуть ниже и вызывающейся три раза – как ты помнишь, в НАГе было три строчки. Зайдя в нее, ты увидишь нашу старую знакомую Call_Ortho, которая здесь выполняет ту же функцию и, вообще, близлежащий код напоминает нам код вывода прозрачного четырехугольника. В этом нет ничего странного (я же предупреждал про дождь, снег, огонь, дым, буквы и т.п.).
Оставшиеся лишние байты забьем NOP. НАГ-скрин станет прозрачным, как человек-невидимка. Если у тебя конкретные проблемы с ASSEMBLERом, то редактировать код будет проще в OllyDbg. Вот тебе небольшая инструкция:
Разберем по косточкам скринсейвер «3D Formula 1 Screensaver». Работать будем по шаблону. Ищем glOrtho, которая также находится в функции обертке, переименовываем ее в CallOrtho… ищем и обламываемся - ссылок на функцию слишком много, анализировать все не хочется. То же самое касается и Color4f и Vertex3f. Все это говорит о том, что программа далека от совершенства. Ну да ладно, мы сюда залезли не для оптимизации кода. Попробуем сменить тактику: вспомним третий признак прозрачного НАГа, который гласит, что его код должен быть в конце функции визуализации. В программах, использующих OpenGL, функция визуализации (RenderFunc – так я ее обзываю в IDA) должна заканчиваться API-функцией SwapBuffers или glutSwapBuffers, если прога использует библиотеку-посредника glut, но обычно более-менее серьезный софт под Windows библиотеки-посредники не используют. Что делает SwapBuffers, видно из ее названия. Когда в один буфер происходит рисование, содержимое другого отображается на экране, а функция меняет их местами (в общем, она нужна для анимации). Ищем SwapBuffers и находим:
Всего два вызова, заглянем в каждый. Лично мне понравился второй:
Так как нехорошо показывать НАГ зарегистрированному пользователю, логично предположить, что в любой проге с НАГом должен быть механизм его обхода. Можно не затирать функции НАГа, а найти этот механизм и «выломать из него шестеренку». Итак, мне понравился условный переход по адресу 0x00417A41, который обходит вызов функции sub_4065BB. Заглянем сюда и увидим, что здесь вызывается и наш Call_Ortho, и glColor4f, и glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA), и много других интересных функций, о которых можно почитать в мануалах по OpenGL, щедро разбросанных по Сети. Обойдем вызов sub_4065BB путем классического 75 -> EB. Шестнадцатеричное представление АСМ команды jne 000417a54 (7511) меняем 7511 на EB11. Теперь условный переход (в зависимости от значения ECX) становится безусловным Jmp. Запускаем и убеждаемся что «шестеренка выломана», а механизм вывода НАГа работает как надо. Переспективный DirectX.Главная проблема, с которой мы столкнемся при исследовании программ, использующих библиотеку DirectX - это технология COM. Грубо говоря, технология заключается в том, что функции библиотеки вызываются через указатель на массив указателей на функции. Напоминает механизм вызова виртуальных функций. Неудивительно, так как COM на этом и основывается. В результате, дизассемблер не имеет возможности идентифицировать вызов функции DirecX, что несколько усложняет наши исследования и заставляет пересмотреть стратегию нападения. Из приведенных трех признаков НАГа, которыми мы пользовались при исследовании OpenGL-программ, сейчас для нас актуальным остается только признак номер три. Как ты скоро увидишь, не так уж это и мало. Как найти RenderFunc? Для этого придется разобраться, куда обычно 3Д-кодеры ее вставляют. А вставляют они ее обычно в функцию (в IDA я ее обзываю On_Idle), которая вызывается из цикла выборки сообщений в момент простоя программы. Это не единственный способ вызова RenderFunc, но вменяемые программисты чаще используют его. Возьмем первый DirectX-пример: «Spirit of Fire 3D Screensaver». Грузим исполняемый файл в IDA. Значит, первое, что нужно сделать – найти RenderFunc. Ищем цикл выборки сообщений, вернее, любую API-функцию, входящую в эту конструкцию, например PeekMessage (как правило, именно PeekMessage применяется в циклах выборки совместно с RenderFunc). Итак, нашли оба вызова в одной функции sub_40A4A0, соответственно, по адресу 0x0040A4A0. Ищем On_Idle. Задача не сложная – после WaitMessage только один Call sub_403910. Переименовываем в OnIdle, заглядываем в нее и спускаемся вниз. Что у нас здесь? Ага, несколько условных переходов (в листинге я их пронумеровал от 1...3). Целых 3, если точнее. Конечно, можно, тупо начиная с конца, поочередно делать их безусловными, а можно заглянуть в функции, которые эти Jcc обходят.
Итак, заходим в первую sub_401110, вызываемую из 0x00403C3D, – и видим, кроме API-функций, (ExtTextOut, SetTextColor...), работающих с текстом, еще и ссылки на знакомые до боли строки «UNREGISTERED VERSION!», «Press the space bar to find out». Ничего не напоминает? Это и есть функция, рисующая НАГ. Теперь зайдем в sub_401390 из 0x00403C44. Там ты тоже увидишь ссылку на интересную строку «Remaining time: %u sec.». Все ясно – это функция-таймер, отвечающая также и за его отображение. На основании исследований, делаем заключение – для нормального функционирования скринсейвера обе функции не нужны, соответственно, Jnz под номером (1) наш искомый переключатель в мир лицензионного софта, далее классика: 75 -> EB. Следующий пример про Атлантиду – «Atlantis 3D Screensaver». Ищем цикл выборки сообщений с помощью вышеописанного способа, быстро ее находим (начинается с 0x0042414F). Ежу понятно, что единственный вызов собственной функции (не WinAPI) – это On_Idle (адрес вызова 0x004241B3). Заходим в нее в поисках RenderFunc. Привычно спускаемся вниз и... обламываемся:
Что это? Где искать эту функцию? Для начала надо разобраться, что у нас в EAX. Поднимаемся немного выше по коду:
далее ищем что в var_C, ползем в самое начало On_Idle:
Чтоб разобраться, что находится в ECX, придется подниматься еще выше, т.е. вернуться назад в цикл выборки сообщений и посмотреть, что делается с ECX.
Посмотрел? Тогда продолжаем подниматься выше по коду:
Опять этот ECX и опять нужно выходить из функции (sub_423E9B) наверх. В общем, скоро ты всплывешь здесь:
Поздравляю! Ты только что познакомился с тем, как выглядит объектно-ориентированное программирование в самом низу, на уровне ASSEMBLER'а. Значение, так назойливо передающееся в ECX во все нами пройденные функции, это аналог this в С++, а функция, которую мы ищем, виртуальная (адрес функции = (this+4Ch)). Все это, конечно, прекрасно, но как найти адрес виртуальной функции? this=unk_56EF30 не инициализирован. К сожалению, тут не обойтись без отладчика. Грузим скрин в OllyDbg, трассируем прогу, пока в 0x0056EF30 (правой кнопкой в окне данных, Go to -> Expression, ввести адрес 0056EF30) не появится значение – у меня это 0x18744900. Переставляем байты с учетом специфики процессоров Intel (младший байт по младшему адресу), получаем указатель 0x00497418, прибавляем смещение 0x00497418 + 0x4C = 0x00497464, переходим по нему и, наконец, получаем адрес 0x00454a87 виртуальной функции. Находим ее в IDA: небольшой размер sub_454A87 наводит на мысль, что НАГ прячется в другой функции, вызываемой отсюда. Попробуем определить, в какой, путем банального NOPа. Как всегда, начнем с конца функции. Здесь надо внести некоторые пояснения. Обычно нет смысла тратить время на самый последний Call. Не забывай о тотальной победе объектно-ориентированного программирования над процедурным, благодаря которой тело современной функций нашпиговано вызовами конструкторов, деструкторов и прочими инициализирующими и очищающими функциями. Начнем с предпоследней функции (помни про параметры), правим, запускаем… - мимо! Нопим следующий вызов (0x00454B0C) с параметром (0x00454B08), запускаем и попадаем. Так-с, НАГа нет, но нет и часов и FPS'а. Кого-то устроит этот результат, но не нас. Все равно найдем НАГ! Заходим в sub_428670 и спускаемся вниз, не забыв отметить про себя длину листинга – около 3 метров. Здесь будем использовать метод исключения. Суть его в следующем: как я уже упоминал, в программе должен быть условный переход, который отличает зарегистрированного пользователя от остальных. Его-то и будем искать и превращать в безусловный переход. Для этого нужно, начиная с конца функции, выбирать только условные переходы – те, которые исключают большой участок кода, где есть вызовы функций (без них ничего не нарисуешь). IDA сильно упрощает все эти действия. Стоит только кликнуть по переходу, как она подсветит желтеньким его и место перехода. Стоит отметить, что отрицательный результат дает пищу для анализа, позволяя определить, где мы и вообще в том ли направлении роем. Первым я выбираю переход, который исключает примерно метр кода.
После правки в скринсейвере пропадает таймер, но не НАГ. Следующим идет еще метр кода:
Вот и нет НАГа!И последний на сегодня пример – «Christmas Time 3D Screensaver» на новогоднюю тему – будет самым сложным. Сложность заключается в том, что On_iDle и RenderFunc будут совсем не детских размеров. Это несколько усложнит поиск кода создания НАГа. Но только «несколько», так как человека, знающего, чего он ищет и где и как оно выглядит, остановить сложно. Грузим скрин в IDA, ищем On_Idle. Для этого находим цикл, начиная с адреса 0x0041295F. Замечаем, что с циклом что-то не так, а именно – в теле цикла очень большой участок кода. Похоже, изначально On_Idle была определена как inline функция, и все ее содержимое компилятор вставил в цикл. Словом, On_Idle нашли, следующий пункт – RenderFunc. Для поиска RenderFunc используем метод исключения: начнем с Jcc по адресу 0x00413344. Правим, запускаем, – мимо. Правим два перехода по адресам 0x004132DC, 0x004132EE. Оба они ссылаются на один адрес, – и опять мимо. Выбираем Jcc с адресом 0x00413282, ну вот и попали! Значит, одна из двух вызываемых функций из исключенного кода RenderFunc. Зайдем и посмотрим в каждую. Заходим в sub_406C07 - пять локальных переменных, штук пять вызовов функций – нет, это RenderFunc. Заходим в sub_4091FA, вот оно - около 50 локальных переменных, команды сопроцессора, ссылки на строки типа «Bliss», «Lamp_off01.tga»... А дальше – метод исключения, который со второго раза должен показать на условный переход по адресу 0x0040AA18. Кто следующий?Надеюсь, у меня получилось показать, как легко и непринужденно (практически без применения отладчика) можно лишить 3Д-приложение НАГ-скрина. Сложностью будет только применение протекторов, но так как люди, отвечающие за выбор протектора (не знаю кто, разработчики или издатели) страдают хроническим отсутствием фантазии, то, как правило, им является ASProtect. Для снятия его часто достаточно stripper'а. НАГ-скрин в 3D-программе – слишком заметная конструкция, чтоб хоть сколько-то на него рассчитывать, поэтому выход один – не использовать или натягивать НАГ-текстуру на ключевую модель. Самое главное: не делать НАГ прозрачным. Как следствие, – возможность располагать код НАГа в любом месте функции визуализации 3D-сцены. INFOЯ набросал небольшой список литературы для внеклассного чтения (пригодится, если в статье встретились какие- то незнакомые тебе слова типа «цикл выборки сообщений», «альфа канал», «ортогональная проекция», «забить командой NOP опкод 0x90»...):
|