Мы откроем сундук, хотя бы пришлось из-за него умереть...



Теоретические основы крэкинга

 

Что тебя смутит – то ложь

Е. Летов

 

 

За пять лет более или менее активного занятия крэкингом (а именно столько времени прошло с того знаменательного момента, как мне удалось впервые в жизни взломать программу) мне удалось выработать набор методов, позволяющих сравнительно быстро найти подход к большинству программ. Возьму на себя смелость утверждать, что именно эта система наиболее доступна для начинающих, поскольку она базируется не на раскрытии алгоритма работы защиты в целом, а на анализе сравнительно небольших участков кода и установлении взаимосвязи между ними.

 

Несмотря на то, что крэкеры, в большинстве своем, работают под одной хорошо известной операционной системой и пользуются сходными инструментами, я буду делать упор не на описание этих инструментов и правил работы с ними, а на универсальные идеи (хотя в качестве примеров я, понятное дело, буду приводить то, что наиболее доступно для самостоятельных экспериментов). Думаю, что Вы сами определитесь, какие инструменты Вам больше по душе.

 

Предлагаемую мной систему не следует воспринимать как единственно правильную, это не «истина в последней инстанции», но именно эта система помогла мне взломать десятки программ, так что ее эффективность неоднократно проверена и подтверждена длительной практикой. Моя точка зрения такова: одна лишь практическая эффективность может служить критерием того, какие идеи и технические приемы могут и должны применяться крэкером в его «профессиональной» деятельности. Именно поэтому я настоятельно рекомендую отрешиться от любых утверждений о «некрасивости» тех или иных приемов борьбы с защитами и ориентироваться лишь на достижение конечной цели – независимо от того, является ли этой целью раскрытие алгоритма работы какого-либо куска кода, или же простое снятие триальных ограничений в программе.

 

Так или иначе, статей от том, «что делать», то есть как взломать конкретную программу или тип защиты, во много раз больше, чем руководств «как и почему надо делать именно это». Образно говоря, статьи о том, «что делать» - это уровень начальной школы, «почему был выбран именно такой прием» - уровень выпускных классов. Но ведь есть еще и высшая школа - изучение идей, которые не привязаны к каким-либо программам и типам защит, но могут быть адаптированы для решения любой конкретной задачи. Выше – только «научная работа», то есть область чистого творчества, генерация оригинальных идей, и никакой «учебник» в этой области принципиально невозможен. По моему глубокому убеждению, основная проблема, возникающая у начинающих крэкеров, заключается в огромном количестве пособий уровня «делай, как я», совершенно не объясняющих, почему автор пособия поступил именно так. В результате начинающий крэкер «на ура» ломает новые версии одной и той же программы, но теряется перед подобной же защитой, но реализованной слегка иначе. Разумеется, существуют и весьма качественные «учебники», предлагающие именно систему, а не только набор технических приемов (те же +ORC Cracking Tutorialz или руководство от the Keyboard Caper’а) – но абсолютное большинство их них написаны на английском языке. Поскольку каждый человек имеет право получать необходимую информацию на своем родном языке (а для меня, как и для многих других, родным языком является русский), рано или поздно должны были появиться русскоязычные тексты, систематизирующие опыт крэкинга. Именно такой материал я и старался написать, а насколько хорошо это у меня получилось - решать вам.

 

 


Глава 0.

Дзен-крэкинг. Теория.

 

Основной идеей дзен-крэкинга (именно это название широко используется на сайте Fravia для обозначения той системы крэкинга, о которой я рассказываю) стало: «я не знаю, как это работает, но я могу это сломать». Разумеется, речь не идет об абсолютном незнании того, как функционирует программа – знание команд ассемблера, способов передачи параметров в функции и процедуры, назначения системных вызовов ОС, особенностей кодогенерации определенных компиляторов и многого другого, несомненно, является обязательным. Более того, это основы, без которых любое изучение внутренностей программ в принципе невозможно – нельзя получить информацию из книги, не понимая языка, на котором она написана. «Не знаю, как работает» следует понимать в том смысле, что очень часто для успешного взлома программы совершенно необязательно проводить доскональный анализ всех защитных процедур. Иметь возможность сказать: «я знаю, для чего нужен каждый байт в этой программе» - это, конечно, хорошо, но на практике вполне успешно работает модель «черного ящика», когда нам известно назначение отдельных процедур, взаимосвязь между ними и то, какие эффекты вызывает передача тех или иных параметров на входы «черного ящика».

 

В крэкинге есть два пути. Первый – путь глубокого анализа, изучения и достижения понимания того, как работает программа. Этот путь весьма надежен, но для получения гарантированного результата он требует много времени, усилий и практического опыта. В наше время главным критерием является эффективность и скорость взлома, а не «правильность», которая интересна лишь «гуру» и вечно недовольным теоретикам. К тому же, если Вы только начали обучаться крэкингу, у Вас может просто не оказаться нужных знаний и опыта, чтобы знать, в каком направлении нужно двигаться. Итак – налицо парадокс: чтобы приобрести опыт, нужно ломать программы, причем ломать успешно, и чем больше – тем лучше, а ломать их не получается из-за недостатка опыта. Но существует второй путь – исследовать программы, исходя из предположений, которые, в свою очередь, строятся на наблюдении за внешними эффектами, производимыми программой. То есть Вы не сразу начинаете выяснять, что и как происходит в недрах кода программы, а сначала строите предположения, «на что это может быть похоже», «как это может быть реализовано» и «как бы я добился такого эффекта, будь я на месте автора программы». Как ни странно, при использовании этого метода, успех зависит не только от знаний, но и от того, насколько богато Ваше воображение. Эффективность дзен-крэкинга опирается, прежде всего, на наблюдения и смелые предположения. Поэтому не надейтесь, что авторы защит будут применять избитые приемы, которые можно аккуратно переписать на бумажку и составить «инструкцию по взлому». Ожидайте неожиданного!

 

Однако Вы не можете строить предположения о работе программы на пустом месте – Вам нужен некий стартовый набор знаний. Поэтому Вы должны собрать как можно больше информации о самой программе – выяснить, упакована она или нет, какие ограничения содержатся в незарегистрированной программе и как выглядит процесс регистрации; узнать, что будет, если Вы попытаетесь использовать программу дольше, чем это предусмотрено триальными ограничениями; проанализировать, какие текстовые строки и ресурсы содержатся внутри программы; поинтересоваться, к каким файлам и ключам реестра программа обращается при запуске, и многое другое. Не поленитесь заглянуть в справочную систему программы – там Вы можете найти описания различий между зарегистрированной и незарегистрированной версией. Значительная часть этой информации Вам скорее всего не пригодится, но Вы не можете знать заранее, какой путь окажется наиболее удобным и какие знания о программе Вам понадобятся. Более того, почти наверняка все необходимые данные о работе программы Вы с первой попытки не соберете, и уже в процессе изучения кода Вам придется возвращаться к этому этапу, чтобы узнать, например, сколько с какими параметрами программа в заданной точке вызывает функцию создания файла, куда считываются введенные серийные номера, сколько раз и откуда программа вызывает функцию проверки регистрации и т.п. А потому – собирайте информацию!

 

 

Когда Вы сгенерировали идеи о том, как именно могут работать интересующие Вас механизмы в программе, перед Вами встанет задача добраться до кода, который эти механизмы реализует. Для этого Вам нужно проанализировать все предполагаемые варианты и найти, к чему можно «прицепиться» в каждом случае. Иными словами, Вы должны представить, чем может отличаться интересующий Вас кусок кода от множества других кусков, и чем более явными будут эти отличия, тем легче Вам будет этот код найти. Например, если программа выводит nag-screen, можно «прицепиться» к функциям создания и отображения окон; если предполагается проверка CRC файла, результатом будет либо многократное чтение небольших блоков, либо загрузка или отображение всего файла в память; если в заголовке окна программы большими буквами написано UNREGISTERED, можно поискать эту строчку в программе и выяснить, откуда и при каких условиях происходит обращение к этой надписи.

 

И, наконец, Вам придется разработать практические приемы, при помощи которых можно добраться до интересующих Вас кусков кода, выбрать подходящий инструмент из своего арсенала и правильно его применить.

 

Как это работает на практике? Представьте себе программу, которая делает нечто. Например, отказывается запускаться после 30 дней использования, выдавая стандартное окошко с сообщением (широко известное как MessageBox). Чтож, у нас есть первое наблюдение. Простой перевод системного времени не помогает – обмануть программу и заставить ее работать дольше, чем положено, не удается. Это второе наблюдение. Из него следует, что программа проверяет текущую дату не на основе показаний внутренних часов Windows. Предполагаем, что программа либо уже сделала пометку «больше не запускаться» где-нибудь в реестре или на диске, либо все-таки определяет текущее время, но каким-либо хитрым способом. Например, читая дату последнего доступа или модификации какого-либо файла. У нас уже есть целых два смелых предположения, которые можно брать за основу в дальнейшем расследовании вредительской деятельности защиты. Если программа не просто «задумывается» при запуске, но еще и шуршит винчестером, вероятность второго варианта сильно повышается. Теперь начинаем проверять эти варианты. В первом случае нам однозначно проще докопаться до истины, установив точки останова на все виды MessageBox’ов и выяснять, какой из условных переходов позволяет избежать появления этого сообщения. Во втором случае в качестве отправной точки можно использовать всевозможные GetFileTime, CompareFileTime (а чем не способ – сравнить дату создания файла программы, т.е. дату инсталляции с датой последней модификации какого-либо файла) и FindFirstFile/FindNextFile (они ведь тоже способны читать временные характеристики файлов).

 

Абсолютное большинство защит, как бы аккуратно они не были реализованы, все-таки имеют «ахиллесову пяту». Эта уязвимость может быть запрятана глубоко в недрах кода, размазана по нескольким десяткам процедур или же быть совершенно неочевидной – но она есть. Стоит только ее обнаружить и нанести точный удар – и защита развалится, как карточный домик. Следовательно, залогом успешного взлома является отыскание уязвимых мест в защите.

 

 

Теперь, когда мы знаем, что нам нужно искать, осталось только определить, как выглядят эти уязвимости. Наиболее «удобные» для крэкера дыры – это прежде всего глобальные переменные, в которых хранится состояние программы («зарегистрирована - не зарегистрирована»), функции, возвращающее критичное для защиты значение (число запусков или дней до истечение триала, результат проверки серийного номера на правильность) и процедуры, выводящие сообщение об успешной или неуспешной попытке регистрации, а также об истечении триального срока программы. Одним из величайших «шедевров», встреченным мной, была глобальная переменная в секции инициализированных данных. По умолчанию ее значение было равно нулю, и менялось на единицу если серийный номер, извлекаемый из реестра, был верен. Исправление одного-единственного бита превратило программу в зарегистрированную. Другим перспективным приемом, который, правда, эффективен в основном против ограничений максимального/минимального значения какого-либо числового параметра (количества обрабатываемых документов, числа запусков и т.п.) является поиск константы, с которой производится сравнение, и модификация либо самой константы, либо условия проверки. Более подробно о том, как обращаться с переменными и константами, я расскажу в соответствующей главе.

 

«Регистрация» программ, где защитная функция возвращает единственное несколько сложнее – в этом случае требуется выявить все точки, в которых функция возвращает какое-либо значение, и подправить это значение. Нужно только помнить, что куски кода вроде

 

xor eax,eax

ret

 

могут встречаться в теле функции в нескольких экземплярах, и обезвредить нужно их все. Да и возвращаемое значение совершенно не обязательно является ноликом или единичкой.

 

Другая проблема для программистов защит, которая сильно облегчает жизнь крэкерам (вот уж воистину «что русскому хорошо, то немцу - смерть») это «проблема условного перехода». Эта проблема заключается в том, реализовать проверку какого-либо условия, не использовав команду условного перехода, не так уж просто. Чем же так выделяются команды условных переходов? А тем, что любой такой переход очень просто превратить в такой же, но с противоположным условием – обычно для этого достаточно исправить ровно один бит! Несмотря на техническую простоту, правка условных переходов все-таки менее предпочтительна, чем модификация функций. Причина этого в том, что условных переходов, имеющих отношение к защите, в программе может быть довольно много (обычно – заметно больше, чем кусков кода, отвечающих за возврат результата функции), и их поиск требует особой внимательности. К тому же, если защита вместо обычных функций использует inline-функции или макросы, разбросанные по всей программе, защитный механизм выглядит как длинный и внешне однородный кусок кода, поиск «плохих» переходов внутри которого несколько затруднителен. С другой стороны, если в таких защитных вставках используются вызовы каких-либо «нормальных» (не inline) функций, особенно функций WinAPI, найти такие идентичные куски становится не так уж сложно. В таком блоке кода почти наверняка есть комбинации команд, по которым такой кусок кода можно идентифицировать – так что поможет либо поиск в двоичном файле программы с использованием маски, либо в дизассемблированном тексте - с использованием регулярных выражений. Если запастись терпением, можно даже проверить все подозрительные участки программы вручную, это вполне реально, если таких участков в программе не больше двух десятков.

 

Теперь Вам известны наиболее часто встречающиеся в защитах дыры, в выявлении которых заключена половина успеха крэкера. Пришло время рассмотреть трудности и «подводные камни», которые могут ожидать Вас в нелегком кэкерском труде. Прежде всего это проблема неверной интерпретации собранной информации. Например, Вы обнаружили, что программа поддерживает использование плагинов и при запуске сканирует все файлы с расширением DLL в собственной директории. Вы вполне логично предполагаете, что программа строит список плагинов для дальнейшей загрузки и подключения. А потом Вы можете очень долго искать механизм определения даты первого запуска – и не найти его. Потому что он уже отработал – как раз при поиске плагинов. Как такое может быть? Да очень просто: в комплект программы включается как минимум один плагин. Далее – обычная привязка к дате последней модификации файла этого плагина; саму дату модификации файла несложно получить в ходе поиска через FindFirst/FindNext. Вот так иногда авторы защит прячут свой вредительский код, что называется, «на самом видном месте».

 

Другой пример неверной интерпретации собранных данных встречается еще чаще; более того, это неизбежное следствие подхода, принятого в дзен-крэкинге. Если Вы нашли условный переход, который начисто «выключает» все сообщения о незарегистрированности программы, из этого не обязательно следует, что программа будет вести себя в точности как зарегистрированная. Убедиться в стопроцентной надежности (или принципиальной неправильности) исправления этого перехода – подчас задача много более сложная, чем найти этот самый условный переход. Что интересно, это утверждение работает и в обратную сторону: если Вы что-то сделали, но не увидели результат, это совершенно не означает того, что Вы ошиблись. Создатели защит не так уж редко создают многоуровневую оборону, и для одержания победы недостаточно разрушить внешние рубежи защитного кода. Если вы что-то сделали, но не получили желаемого результата, не стоит сразу же бросать избранный путь; возможно, что Вы абсолютно правы и необходимо двигаться тем же путем и дальше. Даже если после Ваших манипуляций подопытная программа рушится с «ужасным» GPF, этот GPF может быть всего лишь еще одной, еще не выявленной уловкой создателя защиты.

 

 


Глава -1.

Орудия крэкера.

 

Ваше слово, товарищ «Маузер»

В. Маяковский

 

Осмелюсь предположить, что Вы читаете этот текст не из праздного интереса или ради абстрактного «знания», а хотите научиться применять эти знания на практике. То есть ломать программы. И хотя это пособие носит название «Теоретические основы…», Вы не замедлите применить эти «основы» на практике. А поскольку крэкинг отнюдь не ограничивается теорией, Вам потребуются «рычаги», при помощи которых Вы сможете перевернуть код. И именно об этих «рычагах» и пойдет речь в данной главе. Раз обучение крэкингу требует постоянной и разнообразной практики, было бы логично начать с перечисления того, что Вам потребуется для «практических занятий». Но, с другой стороны, вряд ли Вы сможете выбрать наилучшие инструменты, не зная хотя бы в общих чертах особенностей Вашей будущей деятельности. И именно поэтому глава носит номер «минус один», но следует за «нулевой» главой, в которой я попытался объяснить, чем Вы будете заниматься и какие трудности могут Вас ожидать.

 

Инструменты – это материальная основа крэкинга, без инструментов в крэкинге – никуда, но сами по себе инструменты – не более чем набор байтов, и лишь человеческая мысль оживляет их, заставляя творить чудеса. Для использования этих программ, как правило, требуются соответствующие знания. Насколько будет Вам полезен лучший отладчик в мире, если Вы не знаете, что означают те хитрые цифры и буквы, которые он показывает на экране? Поэтому прежде чем задать программе вопрос, Вы должны быть уверены, что сможете понять ответ.

 

Но, тем не менее, от качества этих инструментов будет во многом зависеть скорость и эффективность Ваших действий. Ваш инструментарий должен постоянно обновляться; не надейтесь, что софт десятилетней давности сможет чем-то Вам помочь – в области высоких технологий радикальные изменения могут произойти за считанные дни. Программы совершенствуются, наращивают мощность, обрастают новыми полезными и бесполезными функциями; защиты также не стоят на месте – и Вам нужно быть в курсе этих процессов, своевременно обновляя свой арсенал.

 

К вашему счастью, инструментов, пригодных для использования в крэкинге, не так уж мало, и проблема состоит не в том, чтобы их раздобыть, а в том, чтобы отобрать из них наилучшие, изучить их возможности и определить для себя, в какой ситуации тот или иной инструмент лучше всего применить. Поясню эту мысль на примере: на данный момент наиболее мощным из дизассемблеров является IDA Pro, которая способен не просто дизассемблировать код, но и находить в нем вызовы стандартных функций тех или иных компиляторов. Однако если мне необходимо покопаться в программе, написанной на Delphi версии выше третьей, я наверняка не буду использовать IDA Pro, предпочтя ему Delphi Decompiler. Почему? Во-первых, скорость работы IDA Pro и DeDe различается в десятки раз в пользу последнего; используя DeDe, я скорее всего получу желаемый результат раньше, чем закончилось бы дизассемблирование в IDA Pro. Во-вторых, DeDe позволяет анализировать работающие процессы «на лету», что позволяет анализировать сжатые программы, не отвлекаясь на распаковку, восстановление таблицы импорта и прочие вспомогательные действия. Не углубляясь в дальнейшее перечисление достоинств DeDe, начисто отсутствующих в IDA, скажу, что в большинстве случаев специализированная программа позволяет решать свой «родной» класс задач значительно эффективнее, чем программы «общего назначения», ориентированные на ручную работу. Конечно, никто не запретит вам распаковывать программы, вручную копируя секции из памяти в файл, но не проще ли воспользоваться дампером?

 

Наверняка найдутся те, кто возразит, что широкое использование готовых утилит якобы мешает самостоятельному мышлению, чрезмерно упрощает процесс взлома и вообще «настоящие хакеры дизассемблируют в уме». Так вот, мое принципиальное мнение по этому поводу такое: используйте все программы, какие только сочтете нужными, если это поможет вам добиться желаемого. В конце-концов, цель крэкера обычно состоит в получении работающей программы, а никак не в тренировке памяти или демонстрации собственной крутизны. А всем «настоящим хакерам» посоветуйте дизассемблировать в уме winword.exe из состава самого последнего MS Office, и до тех пор, пока они это не сделают, не беспокоить вас древними суевериями. Разумеется, рано или поздно Вы перейдете от использования чужих программ к написанию собственных патчеров, распаковщиков, дамперов и прочих утилит – но такой переход должен быть продиктован насущной необходимостью, а не обезьяним «чтобы быть не хуже других». В конце-концов, большинство программ было написано именно для того, чтобы люди ими пользовались.

 

Итак, какие инструменты и для чего нам потребуются?

 

  1. Отладчики и дизассемблеры. Традиционно оба этих инструмента используются в паре, поскольку дизассемблер выдает лишь «чистый код», хотя современные дизассемблеры способны также распознать вызовы стандартных функций, выделить локальные переменные в процедурах и предоставляют прочий подобный сервис. Пользуясь дизассемблером, Вы можете лишь догадываться о том, какие данные получает та или иная функция в качестве параметров и что они означают; чтобы выяснить это, чаще всего требуется изучения если не всей программы, то довольно значительной ее части. Отладчики выполняют принципиально иные функции, они позволяют анализировать код в процессе его работы, отслеживать и изменять состояние регистров и стека, править код «на лету» - в общем, наблюдать за «личной жизнью» программы и даже активно в нее вмешиваться. Обратной стороной медали является «неинтеллектуальность» многих отладчиков – их врожденные способности к анализу кода редко простираются дальше определения направления перехода. SoftIce, «лучший отладчик всех времен и народов», например, ничего не знает о типах данных и не способен отличить обычный DWORD от указателя на ASCIIZ-строку, хотя и предоставляет пользователю возможность проверить это вручную. Впрочем, вполне реальны отладчики, сочетающие высокое качество дизассемблирования и анализа кода с широкими возможностями по его отладке. Примером может служить OllyDebug, выполняющий эвристический анализ кода, выделяющий локальные переменные, «знающий» о типах данных, передаваемых функциям WinAPI и при этом способный во многих случаях автоматически отличить обычное число от указателя на строку.
  2. Декомпиляторы и узкоспециализированные отладчики. С ростом мощности ЭВМ довольно широкое распространение получили компиляторы, создающие не «чистый» машинный код, а некий набор условных инструкций, который выполняется при помощи интерпретатора. Интерпретатор может как поставляться отдельно (Java), так и быть «прикрепленным» к самой программе (хотя формально не интерпретатор прикрепляется к программе, а программа к интерпретатору. Примером может служить Visual Basic при компиляции в p-code). Возможны и более экзотические варианты, например, применяемый в Форте «шитый код»; компиляция программы в цепочку команд push\call или преобразование текста программы в такую цепочку непосредственно при запуске этой программы. Интерпретаторами являются практически все инсталляторы (в их основе лежит интерпретатор инсталляционного скрипта, хотя сам процесс создания такого скрипта может быть скрыт при помощи использования визуальных средств). Да и «обычные» компилирующие языки могут создавать код, прямой анализ которого весьма затруднителен. Для анализа таких программ используются специализированные утилиты, переводящие код, понятный лишь интерпретатору, в форму, более удобную для понимания человеком. Также некоторые декомпиляторы могут извлекать информацию об элементах интерфейса, созданных визуальными средствами. В любом случае, не следует ожидать от декомпиляторов восстановления исходного текста программы; если декомпилированная программа успешно компилируется и сохраняет работоспособность – это исключение, а не правило.
  3. Распаковщики и утилиты для дампинга процессов. Дизассемблировать запакованную или зашифрованную программу невозможно, но если очень хочется получить хоть какой-то листинг, можно попытаться извлечь из памяти компьютера «снимок» (дамп) программы в момент ее работы. Этот дамп уже можно более или менее успешно дизассемблировать. Более того, на основе дампа можно воссоздать исполняемый файл программы, причем этот файл будет успешно загружаться, запускаться и работать. Именно на этом принципе и основана работа большинства современных распаковщиков: подопытная программа запускается под управлением распаковщика; распаковщик ждет некоторого события, говорящего о том, что программа полностью распаковалась, и тут же «замораживает» программу и сбрасывает ее дамп на диск. Защитные системы нередко пытаются противодействовать получению работоспособного дампа при помощи манипуляций с сегментами и таблицами импорта-экспорта. В этих случаях приходится PE-реконструкторы, т.е. утилиты, обнаруживающие в дампе некорректные ссылки на функции и пытающиеся их восстановить. Многие продвинутые дамперы и распаковщики имеют встроенные средства восстановления секций импорта.
  4. Утилиты анализа файлов. Очень часто требуется быстро узнать, каким упаковщиком или защитным софтом обработана та или иная программа, найти все текстовые строки в дампе памяти, просмотреть содержимое файла в виде таблицы записей, вывести в файл список импортируемых и экспортируемых программой функций и многое другое. Для всех этих целей существует огромное количество специализированных утилит, позволяющих быстро проанализировать файл на наличие тех или иных признаков. Эти утилиты, как правило, не являются жизненно необходимыми, но, при надлежащем качестве, способны сэкономить огромное количество времени и сил.
  5. Шестнадцатеричные редакторы и редакторы ресурсов. Это, несомненно, наиболее древние (по идее, но не обязательно по исполнению) из программистских инструментов, ведущие свою славную историю с тех времен, когда программисты еще умели «читать» и править исполняемый код, не прибегая к помощи дизассемблеров. Тайное искусство чтения кода еще сохранилось в отдаленных уголках Вселенной, но в последние годы практически утратило актуальность. И теперь лишь крэкеры практикуют этот мистический обряд, в соответствии со священной традицией исправляя в неправильных программах идеологически чуждый опкод 75h на истинный и совершенный 0EBh. Редакторы ресурсов, в принципе, занимаются тем же самым, но по отношению к прилинкованным к исполняемому файлу ресурсам. Именно при помощи редакторов ресурсов выполняется значительная часть работ по «независимой» русификации программ и доработке интерфейсов. Рука об руку с редакторами идут всевозможные патчеры, которые позволяют создать небольшой исполняемый файл, автоматически вносящий изменения в оригинальный файл программы либо в код этой программы непосредственно в памяти.
  6. API -шпионы и другие утилиты мониторинга. Очень часто бывает нужно узнать, какие именно действия выполняет та или иная программа, откуда читает и куда записывает данные, какие стандартные функции и с какими параметрами она вызывает. Получить эти знания как раз и помогают утилиты мониторинга. Такие утилиты делятся на две большие группы: те, которые отслеживают сам факт возникновения каких-либо событий и те, которые позволяют выявить один или несколько специфических типов изменений, произошедших в системе за некий промежуток времени. К первой группе относятся всевозможные API-шпионы, перехватывающие вызовы системных (а более продвинутые - и не только системных) функций, хорошо известные утилиты Reg-, File-, PortMon и т.д., перехватчики системных сообщений и многие другие. Эти утилиты, как правило, предоставляют весьма подробную информацию об отслеживаемых событиях, но генерируют весьма объемные и неудобные для анализа логи, если отслеживаемые события происходят достаточно часто. Вторая группа представлена всевозможными программами, создающими снимки реестра, жесткого диска, системных файлов и т.п. Эти программы позволяют сравнить состояние компьютера «до» и «после» некоего события, построить список различий между состояниями и на основе этого списка сделать определенные выводы. Основная проблема при работе с такими утилитами хорошо описывается словами “«после» – не значит «вследствие»” (т.е. кроме интересующей Вас деятельности программы Вы можете получить лог «жизнедеятельности» других параллельно работающих программ и операционной системы).
  7. Прочие утилиты. Существует огромное количество утилит, не вписывающихся в приведенные выше категории или попадающие в несколько категорий. Одна лишь полная классификация этих инструментов потребовала бы значительных усилий, но моя цель не в том, чтобы плодить «мертвые буквы», а в том, чтобы дать Вам знания и навыки, достаточные для самостоятельных занятий. Крэкинг – занятие весьма многогранное, и столь же многогранны инструменты, в нем используемые. Более того, некоторые из утилит, которые могут быть полезны для крэкера, создавались для совершенно иных целей (хорошим примером может служить программа GameWizard32, которая вообще-то была предназначена, чтобы мухлевать в компьютерных играх, но оказалась полезна при вскрытии программы с ограничением на максимальное число вводимых записей). Поэтому еще раз обращу Ваше внимание: важно не только качество инструмента, но и умение творчески и нетривиально его применить.

 

 


Глава 1.

Почти начинаем ломать.

 

Итак, допустим, что у нас есть программа и в ней содержится вредоносный код (далее - «защита»), который нужно обезвредить. Инсталляционный файл программки тихо лежат на нашем винчестере, ожидая того знаменательного момента, когда мы его запустим. Мне вполне понятно Ваше желание немедленно установить эту программу и вступить в битву со злобным и коварным врагом, но охладите на несколько минут свой пыл, и послушайте мой рассказ о процессе инсталляции программ, и о том, чем этот процесс может кончиться.

 

Итак, многие программы в настоящее время поставляются в виде инсталляционного пакета. Для установки программы, как правило, требуется либо запустить один из файлов пакета (это, в частности, отлично всем известные Setup.exe), либо открыть при помощи другой заранее установленной программы (к примеру, файлы с расширением MSI, созданные Microsoft’овским инсталлятором или RPM-пакеты в Linux). В инсталляционные пакеты кроме самих файлов, которые требуется установить на машину пользователя, содержится также описание сценария инсталляции в том или ином виде (назовем это описание сценария для простоты «инсталляционным скриптом», тем более, что чаще всего так оно и есть). Разумеется, инсталлятор может быть и обычной программой, написанной для установки конкретного приложения, но написание собственного инсталлятора – дело достаточно трудоемкое, и потому на практике такие инсталляторы встречаются весьма редко.

 

Инсталляционный скрипт описывает, в каком режиме устанавливается тот или иной файл (добавление, замена, замена с предварительной проверкой версии и т.п.), какие данные необходимо внести в реестр или файлы конфигурации, какие программы запускаются до, в процессе и после инсталляции, а также многое другое. В это «многое другое» могут входить и такие безусловно интересные вещи, как проверка серийных номеров, создание записей в реестре, а также распаковка и запуск программ. О встроенных в инсталляционный скрипт серийных номерах мы поговорим позже. А пока попробуем поразмышлять о том, может заключаться опасность создания ключей в реестре или запуска каких-либо программ.

 

Если Вы взламываете какую-либо программу, оснащенную ограничением на время использования или число запусков, один из «корней зла» может гнездиться именно в инсталляционном скрипте. Представьте себе такую ситуацию: программа хранит дату первого запуска и/или какую-либо иную информацию, необходимую для проверки на истечение срока пробного использования, в реестре. Разумеется, информация закодирована, предприняты меры, чтобы защиту не могло обмануть «подкручивание» системной даты, возможно даже соответствующий ключ реестра хорошо замаскирован (Вам ничего не напоминает мое описание? Да это же ASProtect – ломаный-переломаный, но, как ни странно, все еще популярный). Но, тем не менее, одна лазейка все-таки осталась – если триальный ключ в реестре отсутствует, защита считает, что раньше программа не запускалась. Поэтому защиту можно обмануть, просто удалив из реестра лишние ключики. А теперь представьте, что триальный ключ создается в процессе инсталляции, и если он отсутствует, программа не запускается вообще! Если Вы ставите целью взлома ликвидацию триальных ограничений, Вам могут потребоваться весьма значительные усилия, чтобы отыскать этот маленький, но зловредный ключик в огромном реестре еще более огромной Windows. Как вам нравится такая перспектива? Если встроенных средств инсталлятора оказывается недостаточно для создания триальной «метки», это может быть реализовано при помощи небольшого исполняемого файла, который распаковывается в процессе инсталляции, запускается, делает свое черное дело и сразу после этого удаляется. Упрощенный вариант этого приема может выглядеть как автоматический запуск приложения после окончания инсталляции, чтобы защита смогла создать триальные «метки» на компьютере пользователя.

 

В качестве метки, на основе которой программа будет проверять ограничения по времени или числу запусков, также может использоваться какой-либо файл, создаваемый в процессе инсталляции, особенно если этот файл запрятан где-нибудь глубоко в системных директориях, но при этом активно используется программой. Я встречал защиту, основанную на подобном принципе: дата создания одной из DLL использовалась для отсчета времени с момента установки, а сама DLL была спрятана в системной директории Windows и динамически подгружалась основной программой.

 

Какие средства мы можем применить, чтобы обнаружить и обезвредить эти и другие подобные приемы? Наиболее радикальным средством, разумеется, является декомпиляция инсталляционного скрипта – в этом случае мы получаем практически полную информацию о том, что происходит в процессе установки программы, а в некоторых случаях даже можем повлиять на этот процесс, внеся исправления в инсталлятор. Разобрать инсталляционный скрипт «по косточкам» - задача не самая простая, да и не всегда это необходимо, поэтому на практике чаще пользуются другим типовым приемом, позволяющим обнаружить произошедшие изменения. Этот прием заключается в использовании утилит мониторинга, делающих «снимки» системы (реестра, размеров и дат создания и модификации файлов)  до и после установки и затем анализирующих различия между снимками. Подробный журнал изменений, выдаваемый такими программами, позволяет легко обнаружить подозрительные ключи и файлы, появившиеся в процессе инсталляции. Забегая вперед, скажу, что такие же «снимки» рекомендуется делать и при прохождении других критических периодов работы программы – при первом запуске, при последнем запуске перед окончанием триального срока, при первом запуске после истечения испытательного срока. Установить факт запуска каких-либо программ во время инсталляции можно при помощи утилит, отслеживающих создание и завершение процессов.

 

Однако созданием триальных ключей функции программ, запускаемых в процессе инсталляции, не ограничиваются. Дело в том, что набор функций, поддерживаемых инсталляторами, обычно довольно невелик, и некоторые действия (например, проверку серийного номера с использованием достаточно сложного алгоритма) выполнить средствами инсталляционных скриптов просто невозможно. Один из возможных приемов, применяемых в этом случае – запуск исполняемого файла, который и выполняет все необходимые операции, а затем возвращает управление инсталлятору. В частности, существуют защиты, в которых проверка серийного номера реализована именно так. В некоторых инсталляторах для этих же целей предусмотрен интерфейс, позволяющий использовать плагины (плагин в виде динамически загружаемой библиотеки также упрятываются внутрь инсталлятора, в нужный момент распаковываются во временную директорию и после использования удаляются). Такие исполняемые файлы и плагины, разумеется, невозможно модифицировать напрямую и чаще всего не удается извлечь из инсталлятора для дизассемблирования и изучения, т.к. они хранятся внутри инсталлятора в сжатом виде, а многие коммерческие инсталляторы несовместимы по формату с обычными архиваторами. Если Вы хотите исследовать такой исполняемый файл, Вам почти наверняка потребуется снять с него дамп, чтобы получить материал для загрузки в дизассемблер. Сделать это совсем несложно – запустите инсталлятор под отладчиком и поставьте точки останова на все функции, связанные с загрузкой модулей и библиотек (в случае плагина) или создания процесса (для EXE). В Windows это будут LoadLibrary[Ex], LoadModule[Ex], CreateProcess или устаревший WinExec соответственно. Запомните, откуда инсталлятор пытается загрузить файл, и затем «заморозьте» работу инсталлятора непосредственно перед исполнением этой функции патчем

 

MySelf: jmp MySelf

 

либо манипуляциями с атрибутами соответствующего процесса и потока (SuspendThread). Потом скопируйте нужный файл в надежное место - и можете делать с ним все, что захотите.

 

 

Одним из важнейших искусств для крэкера, несомненно, является изготовление или добыча серийных номеров. Как раздобыть серийный номер для программы, которая без этого номера даже не инсталлируется? Варианты «втихаря списать с лицензионного компакта» или «шантажом и пытками вытянуть из зарегистрированного пользователя», мы рассматриваем как не имеющие ничего общего с высоким искусством крэкинга, и потому изучать их не будем. Серийные номера вообще – это очень обширная тема, но в данном разделе я буду рассматривать только те серийные номера, которые «упрятаны» в инсталлятор. Несколькими строками выше я уже говорил о том, как обращаться с проверщиками серийных номеров, запускаемыми в процессе инсталляции. Теперь посмотрим, как можно решить проблему серийного номера, упрятанного в инсталляционный скрипт.

 

Наиболее удобным для нас был бы вариант с серийным номером, записанным открытым текстом, но так уже практически никто не делает, поскольку «взломать» такую защиту можно при помощи обычного просмотрщика текстов. Так что будем считать, что при проверке правильности используется представление серийного номера как результата некой хэш-функции.

 

Часто в инсталлятор «зашит» не один серийный номер, а несколько – и это способно существенно облегчить нам задачу. Существует три метода проверки серийного номера:

  1. Вычислить хэш-функцию от серийного номера и проверить, удовлетворяет ли результат какому-либо условию.
  2. Пропустить результат через хэш-функцию и сравнить его со списком эталонов.
  3. Пропустить зашифрованные серийные номера, спрятанные внутри программы, через процедуру расшифровки и затем сравнить результат с значением серийного номера, который ввел пользователь. Очевидно, что это наиболее простой случай. Фактически, требуется лишь обнаружить точку, в которой происходит сравнение введенного серийного номера с правильным, и прочитать правильное значение.

 

Различие между п.1 и п.2 не очевидно, но оно есть: именно на втором методе обычно основаны всевозможные «черные списки» серийных номеров. Первый способ проверки в инсталляторах применяется сравнительно редко из-за слабых математических возможностей интерпретаторов инсталляционных скриптов и ориентации на максимальную простоту процесса создания инсталляции (грамотная установка защиты, к нашему счастью, достаточно сложна и при этом все равно не дает гарантированного результата). Так что в итоге внутри большинства инсталляторов упрятан все тот же список серийных номеров (возможно, из одного элемента). Что интересно, абсолютное большинство инсталляторов никак не упаковывают инсталляционные скрипты (хотя исходный текст скрипта вполне может быть откомпилирован в байт-код), поэтому Вы можете без особых сложностей эти скрипты модифицировать.

 

Первое, что приходит в голову – найти, где в инсталляторе спрятан этот самый список. Для этого нужно ввести какой-нибудь серийный номер, потом найти код, сравнивающий значение хэш-функции от введенного серийника со списком и вписать свое значение хэш-функции на место оригинального. Что называется, «просто и со вкусом».

 

Другой способ заключается в том, чтобы идентифицировать программный продукт, при помощи которого сделана инсталляция, затем раздобыть такой же и как следует его проанализировать. Это позволит Вам сравнительно быстро  узнать структуру инсталляционного скрипта, технические возможности инсталлятора (и проблемы, которые эти возможности могут Вам создать), способ генерации и формат хранения правильных серийных номеров, коды и функции внутренних команд интерпретатора скриптов. При желании Вы даже сможете написать распаковщик инсталляционных пакетов, декомпилятор инсталляций а то и универсальный(!!!) генератор ключей ко всем программам, инсталляционные пакеты которых сделаны при помощи исследованного Вами программного продукта.

 

В некоторых случаях наиболее прямым путем к получению полнофункциональной программы из урезанного варианта является именно вскрытие инсталлятора. Поясню эту идею примером. Допустим, что у Вас есть демо-версия какой-либо хорошей программы, но Вам этого мало, и Вы хотите иметь полностью функциональный вариант продукта. При этом у Вас нет никакого желания вручную восстанавливать недостающий код, заботливо выдранный автором. Однако на сайте производителя иногда можно найти обновления для коммерческих версий нужной Вам программы, и эти обновления, как правило, содержат полные и исправленные варианты основных файлов программы. Что из этого следует? А то, что если неким таинственным образом Вам удастся установить апдейт на демо-версию, есть ненулевая вероятность, что свою демонстрационную версию Вы превратите в программу, по функциональности практически не отличающуюся от полной! Разумеется, программа обновления перед своей установкой выполнит проверку на возможность обновления (в том числе и для того, чтобы пользователь случайно не «обновил» демо-версию) – но ведь Вы решили изучать крэкинг именно для решения проблем именно такого рода. Так что Вам нужно лишь немного «доработать» инсталлятор, ликвидировав в нем проверку на возможность обновления.

 

По сути, любой инсталлятор представляет собой самораспаковывающийся архив с достаточно сложным SFX-модулем. Более того, некоторые инсталляционные пакеты даже можно открыть обычными архиваторами! Прямым следствием этого является возможность распаковать содержимое инсталляционного пакета (если, конечно, оно не зашифровано). Даже если ни один из стандартных архиваторов «не берет» инсталляционный пакет, распаковать файлы можно вручную. Дело в том, что инсталлятор обязательно содержит внутри себя процедуру распаковки, и эта процедуру можно попытаться проанализировать. Вам понадобится узнать, где находится процедура распаковки, какие параметры она принимает и что эти параметры обозначают (хотя бы приблизительно). Если Вам это удастся, Вы сможете попытаться принудительно вызвать эту процедуру с нужными Вам параметрами, манипулируя кодом программы и состоянием регистров, и извлечь файлы из пакета. Задача поиска этой процедуры облегчается тем, что в инсталляционных скриптах указание на место, куда будут устанавливаться файлы, хранится в виде текста и, поставив точку останова на чтение данных из этих текстовых строк, Вы сможете обнаружить, откуда происходит обращение к этим строкам. Кстати, даже простой анализ текстовых строк, содержащихся в инсталляционном пакете, способен дать множество полезной информации. А теперь представьте себе программу, которая при установке просит некоторое условие, и, если это условие не выполнено, «забывает» установить пару-тройку файлов или устанавливает вместо нормальных версий этих файлов урезанные. Вот тут-то и нужна возможность заглянуть внутрь архива и извлечь из него нужные файлы.


Глава 2.

Почти начинаем ломать.

 

Мы откроем сундук, хотя бы пришлось из-за него умереть...


Дата добавления: 2019-08-30; просмотров: 158; Мы поможем в написании вашей работы!

Поделиться с друзьями:






Мы поможем в написании ваших работ!