Теоретические основы крэкинга: Глава 0. Дзен-крэкинг. Теория. — Архив WASM.RU
Я знаю, что ничего не знаю
СократИдея, лежащая в основе дзен-крэкинга (именно это название широко используется на сайте Fravia для обозначения той системы крэкинга, о которой я рассказываю), звучит так: "я не знаю, как это работает, но я знаю, как это сломать". Разумеется, речь не идет об абсолютном незнании того, как функционирует программа - знание команд ассемблера, способов передачи параметров в функции и процедуры, назначения системных вызовов ОС, особенностей кодогенерации определенных компиляторов и многого другого, несомненно, является обязательным. Более того, это основы, без которых любое изучение внутренностей программ в принципе невозможно - как нельзя извлечь информацию из книги, не понимая языка, на котором она написана. "Не знаю, как работает" следует понимать в том смысле, что очень часто для успешного взлома программы совершенно необязательно проводить доскональный анализ всех защитных процедур. Иметь возможность сказать: "я знаю, для чего нужен каждый байт в этой программе" - это, конечно, хорошо, но на практике вполне успешно работает модель "черного ящика", когда нам известно назначение отдельных процедур, взаимосвязь между ними и то, какие эффекты вызывает подача тех или иных параметров на входы "черного ящика".
Вообще, в крэкинге существует два пути. Первый - путь глубокого анализа, изучения и достижения понимания того, как работает программа. Этот путь весьма надежен, но для получения гарантированного результата он требует много времени, усилий и практического опыта. В наше время главным критерием является эффективность и скорость взлома, а не "правильность", которая интересна лишь "гуру" и вечно недовольным теоретикам. К тому же, если Вы только начали обучаться крэкингу, у Вас может просто не оказаться нужных знаний и опыта, чтобы знать, в каком направлении нужно двигаться. Итак - налицо парадокс: чтобы приобрести опыт, нужно ломать программы, причем ломать успешно, и чем больше - тем лучше, а ломать их не получается из-за недостатка опыта. Но существует второй путь - исследовать программы, исходя из предположений, которые, в свою очередь, строятся на наблюдении за внешними эффектами, производимыми программой. То есть Вы не начинаете сразу выяснять, что и как происходит в недрах кода программы, а сначала делаете предположения: "на что это может быть похоже", "как это может быть реализовано" и "как бы я добился такого эффекта, будь я автором программы". Как ни странно, при использовании этого метода, успех зависит не только от знаний, но и от того, насколько богато Ваше воображение. Эффективность дзен-крэкинга опирается, прежде всего, на наблюдения и смелые предположения. Поэтому не надейтесь, что авторы защит будут применять избитые приемы, которые можно аккуратно переписать на бумажку и составить "инструкцию по взлому". Ожидайте неожиданного!
Однако Вы не можете строить предположения о работе программы на пустом месте - Вам потребуется некий стартовый набор знаний. Поэтому Вы должны собрать как можно больше информации о самой программе - выяснить, упакована она или нет, какие ограничения содержатся в незарегистрированной программе и как выглядит процесс регистрации; узнать, что будет, если Вы попытаетесь использовать программу дольше, чем это предусмотрено триальными ограничениями; проанализировать, какие текстовые строки и ресурсы содержатся внутри программы; поинтересоваться, к каким файлам и ключам реестра программа обращается при запуске, и многое другое. Не поленитесь заглянуть в справочную систему программы - там Вы можете, к примеру, найти описания различий между зарегистрированной и незарегистрированной версией. Значительная часть этой информации Вам скорее всего не пригодится, но Вы не можете знать заранее, какой путь окажется наиболее удобным и какие знания о программе Вам понадобятся, а какие - нет. Более того, почти наверняка все необходимые данные о работе программы Вы с первой попытки не соберете, и уже в процессе изучения кода Вам придется возвращаться к этому этапу, чтобы узнать, например, при каких условиях программа в заданной точке вызывает функцию создания файла, куда считываются введенные серийные номера, сколько раз и откуда программа вызывает функцию проверки регистрации и т.п. А потому - собирайте информацию!
Когда Вы сгенерировали идеи о том, как именно могут работать интересующие Вас механизмы в программе, начинается этап проверки правильности Ваших предположений. А значит, следующая Ваша задача - добраться до кода, который эти предполагаемые Вами механизмы мог бы реализовывать. Для этого Вам нужно проанализировать все предполагаемые варианты и найти, к чему можно "прицепиться" в каждом случае. Иными словами, Вы должны представить, чем может отличаться интересующий Вас кусок кода от множества других кусков, и чем более явными будут эти отличия, тем легче Вам будет этот код найти. Например, если программа выводит nag-screen, можно "прицепиться" к функциям создания и отображения окон; если предполагается проверка CRC файла, результатом будет либо многократное чтение небольших блоков, либо загрузка или отображение всего файла в память; если в заголовке окна программы большими буквами написано UNREGISTERED, можно поискать эту строчку в программе и выяснить, откуда и при каких условиях происходит обращение к этой надписи.
И, наконец, Вам придется разработать практические приемы, при помощи которых можно добраться до интересующих Вас кусков кода, выбрать подходящий инструмент из своего арсенала и правильно его применить.
Как это работает на практике? Представьте себе программу, которая делает нечто. Например, отказывается запускаться после 30 дней использования, выдавая стандартное окошко с сообщением (широко известное как MessageBox). Чтож, у нас есть первое наблюдение. Простой перевод системного времени не помогает - обмануть программу и заставить ее работать дольше, чем положено, не удается. Это второе наблюдение. Из него следует, что программа проверяет текущую дату не (или не только) на основе показаний внутренних часов Windows. Предполагаем, что программа либо уже сделала пометку "больше не запускаться" где-нибудь в реестре или на диске, либо все-таки определяет текущее время, но каким-либо хитрым способом. Например, читая дату последнего доступа или модификации какого-либо файла. У нас уже есть целых два смелых предположения, которые можно брать за основу в дальнейшем расследовании вредительской деятельности защиты. Если программа не просто "задумывается" при запуске, но еще и шуршит винчестером, вероятность второго варианта сильно повышается. Теперь начинаем проверять эти варианты. В первом случае нам однозначно проще докопаться до истины, установив точки останова на все виды MessageBox'ов и выяснять, какой из условных переходов позволяет избежать появления этого сообщения. Во втором случае в качестве отправной точки можно использовать всевозможные GetFileTime, CompareFileTime (а чем не способ - сравнить дату создания файла программы, т.е. дату инсталляции с датой последней модификации какого-либо файла) и FindFirstFile/FindNextFile (они ведь тоже способны читать временные характеристики файлов).
Абсолютное большинство защит, как бы аккуратно они не были реализованы, все-таки имеют "ахиллесову пяту". Эта уязвимость может быть запрятана глубоко в недрах кода, размазана по нескольким десяткам процедур или же быть совершенно неочевидной - но она есть. Стоит только ее обнаружить и нанести точный удар - и защита развалится, как карточный домик. Следовательно, залогом успешного взлома является отыскание уязвимых мест в защите.
Теперь, когда мы знаем, что нам нужно искать, осталось только определить, как выглядят эти уязвимости. Наиболее "удобные" для крэкера дыры - это прежде всего глобальные переменные, в которых хранится состояние программы ("зарегистрирована - не зарегистрирована"), функции, возвращающее критичное для защиты значение (число запусков или дней до истечение триала, результат проверки серийного номера на правильность) и процедуры, выводящие сообщение об успешной или неуспешной попытке регистрации, а также об истечении триального срока программы. Одним из величайших "шедевров", встреченным мной, была глобальная переменная в секции инициализированных данных. По умолчанию ее значение было равно нулю, и менялось на единицу если серийный номер, извлекаемый из реестра, был верен. Исправление одного-единственного бита превратило программу в зарегистрированную. Другим перспективным приемом, который, правда, эффективен в основном против ограничений максимального/минимального значения какого-либо числового параметра (количества обрабатываемых документов, числа запусков и т.п.) является поиск константы, с которой производится сравнение, и модификация либо самой константы, либо условия проверки. Более подробно о том, как обращаться с переменными и константами, я расскажу в соответствующей главе.
"Регистрация" программ, где защитная функция возвращает единственный результат несколько сложнее - требуется лишь выявить все точки, в которых функция возвращает какое-либо значение, и подправить это значение. Нужно только помнить, что куски кода вроде
Код (Text):
mov eax,0 … retмогут встречаться в теле функции в нескольких экземплярах, и обезвредить нужно их все. Да еще то, что возвращаемое значение совершенно не обязательно является ноликом или единичкой.
Другая проблема для программистов защит, которая сильно облегчает жизнь крэкерам (вот уж воистину "что русскому хорошо, то немцу - смерть") это "проблема условного перехода". Эта проблема заключается в том, реализовать проверку какого-либо условия, не использовав команду условного перехода, не так уж просто. Зато если этот условный переход есть, его легко можно исправить на такой же, но с противоположным условием - обычно для этого достаточно изменить ровно один бит. Несмотря на техническую простоту, правка условных переходов все-таки менее предпочтительна, чем модификация функций. Причина этого в том, что условных переходов, имеющих отношение к защите, в программе может быть довольно много, и их поиск требует особой внимательности. К тому же, если защита вместо обычных функций использует inline-функции или макросы, разбросанные по всей программе, защитный механизм выглядит как длинный и внешне однородный кусок кода. Поиск "плохих" переходов внутри такого "монолитного" кода может быть довольно затруднителен. С другой стороны, если в таких защитных вставках используются вызовы каких-либо "нормальных" (не inline) функций, особенно функций WinAPI, найти такие идентичные куски становится не так уж сложно. В таком блоке кода почти наверняка найдутся последовательности команд, по которым такие защитные вставки можно идентифицировать - так что поможет либо поиск в двоичном файле программы с использованием маски, либо в дизассемблированном тексте - с использованием регулярных выражений. Если запастись терпением, можно даже проверить все подозрительные участки программы вручную, это вполне реально, если таких участков в программе не больше двух десятков.
Теперь Вам известны наиболее часто встречающиеся в защитах дыры, в выявлении которых заключена половина успеха крэкера. Пришло время рассмотреть трудности и "подводные камни", которые могут ожидать Вас в нелегком кэкерском труде. Прежде всего это проблема неверной интерпретации собранной информации. Например, Вы обнаружили, что программа поддерживает использование плагинов и при запуске сканирует все файлы с расширением DLL в собственной директории. Вы вполне логично предполагаете, что программа строит список плагинов для дальнейшей загрузки и подключения. А потом Вы можете очень долго искать механизм определения даты первого запуска - и не найти его. Потому что он уже отработал - как раз при поиске плагинов. Как такое может быть? Да очень просто: в комплект программы включается как минимум один плагин. Далее - обычная привязка к дате последней модификации файла этого плагина; саму дату модификации файла несложно получить в ходе поиска через FindFirst/FindNext. Вот так иногда авторы защит прячут свой вредительский код, что называется, "на самом видном месте".
Другой пример неверной интерпретации собранных данных встречается еще чаще; более того, это неизбежное следствие подхода, используемого в дзен-крэкинге. Если Вы нашли условный переход, который начисто "выключает" все сообщения о незарегистрированности программы, из этого не обязательно следует, что программа будет вести себя в точности как зарегистрированная. Убедиться в стопроцентной надежности (или принципиальной неправильности) исправления этого перехода - подчас задача много более сложная, чем найти этот самый условный переход. Что интересно, это утверждение работает и в обратную сторону: если Вы что-то сделали, но не получили немедленно желаемый результат, это совершенно не означает, что Вы ошиблись. Создатели защит не так уж редко создают многоуровневую оборону, и в этом случае для одержания победы недостаточно разрушить внешние рубежи защитного кода. Если вы что-то сделали, но не добились успеха, не стоит сразу же бросать избранный путь; возможно, что Вы абсолютно правы и необходимо двигаться тем же путем и дальше. Даже если после Ваших манипуляций подопытная программа рушится с "ужасным" GPF, этот GPF может быть всего лишь еще одной, еще не выявленной уловкой создателя защиты. © CyberManiac
Теоретические основы крэкинга: Глава 0. Дзен-крэкинг. Теория.
Дата публикации 13 дек 2003