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

Вид материалаДокументы

Содержание


Переменные и константы
Подобный материал:
1   2   3   4   5   6   7   8   9   10
Глава 3.

«Критические дни» программ.


Все боится времени, но само время боится Пирамид

Египетская пословица


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


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


Возможны и другие причины, по которым Вам может потребоваться снять ограничение на время использования программы. Для некоторых программ процедура регистрации не предусмотрена в принципе, но при этом в течение испытательного срока они выполняют свои функции в полном объеме (часто такие программы помечены как Demo- и Evaluation-версии), и вполне приемлемым решением было бы неограниченное продление триального срока. Случается также, что необходимо установить бета-версию программы, у которой истек «срок годности», но более новой беты нет, а посмотреть на программу очень хочется. Так или иначе, снятие ограничений на время использования или число запусков программ – одна из актуальных задач современного крэкинга.


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


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


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


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


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


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


Многие из ныне существующих защит создают «метки» в момент первого запуска программы. Факт первого запуска обычно определяется простейшим способом: если программа при запуске не обнаруживает «метку», она считает, что запущена впервые, и создает «метки». Обычно создание «меток» выполняется сразу после начала исполнения программы, но изредка встречаются программы, которые выполняют эти действия при завершении программы (возможно, что существуют защиты, которые делают это в случайный момент времени). Скорее всего, таким образом разработчики защит пытались затруднить выявление защитного кода, но на деле они добились прямо противоположного эффекта: большинство операций по загрузке данных из реестра выполняется именно при запуске или изменении настроек программы, а вот запись в реестр в конце сеанса работы – операция менее распространенная. При завершении сеанса обычно сохраняется только информация о положении окон, настройках интерфейса программы и прочие подобные данные, которые, как правило, несложно отличить от защитных механизмов программы. Очевидно, что если защита использует исключительно «метки», создаваемые при первом запуске, после удаления этих «меток» программа считает, что запущена впервые, и начинает отсчет триального срока заново. Для того, чтобы пользователи не обходили эту защиту совсем уж элементарными средствами вроде удаления соответствующего ключа реестра, ключ, содержащий в себе защитную информацию, может быть «плавающим», то есть имя ключа может генерироваться случайным образом в зависимости от аппаратной конфигурации компьютера или каких-либо иных параметров. Найти вручную такой ключ среди десятков подобных практически нереально. В частности, именно такой механизм использует ASProtect. Поскольку «плавающий» ключ, сколь успешно бы он не был замаскирован, все-таки отличается от окружающих его ключей, определив признаки, которые отличают «плавающий» счетчик от обычных ключей реестра, возможно создать программу, которая бы автоматически выявляла подозрительные ключи, что подтверждается наличием как минимум трех независимо разработанных утилит, способных выявлять и удалять ключи, созданные ASprotect’ом. В любом случае, изменения в реестре, возникшие после первого запуска программы, нетрудно обнаружить при помощи программ мониторинга реестра.


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


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


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


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

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

  1. Сделать снимки состояния системы до и после установки программы.
  2. Сделать снимок системы перед первым запуском программы, запустить программу, сразу же закрыть ее (чтобы в снимок системы не попали изменения, возникающие при работе с программой и не имеющие отношения к функционированию защиты) и тут же сделать еще один снимок системы. Если программа запускается после инсталляции автоматически, и выполнить этот пункт не представляется возможным, мы, тем не менее, сможем обнаружить изменения, произошедшие при первом запуске, сравнив снимки, сделанные в предыдущем пункте (другое дело, что мы не сможем отличить изменения, произошедшие при инсталляции, от модификаций, внесенных в систему программой при первом запуске).
  3. Запустить программу во второй раз, сделав снимки до запуска и после завершения программы. Если программа содержит ограничение на число запусков, путем несложного анализа мы сможем легко выявить счетчик запусков. Если программа содержит ограничение по времени использования, то мы можем установить, использует ли программа какие-либо дополнительные механизмы отслеживания времени. О наличии таких механизмов может свидетельствовать появление в реестре новых ключей или изменение уже существующих, появившихся в ходе инсталляции или первого запуска. Если такие ключи обнаружатся, есть вероятность, что программа кроме даты первого запуска отслеживает еще и дату предыдущего запуска, число дней, в течение которых использовалась программа или другую подобную информацию. Для максимальной надежности и достоверности результатов эту операцию можно повторить несколько раз.
  4. Выяснить, какие изменения в системе происходят, когда программу запускают впервые после истечения триального срока. Это необходимо для поиска меток, которые могли бы воспрепятствовать нормальной работе программы после окончания времени, отведенного на ее пробную эксплуатацию. Важно отметить, что для программ с ограниченным числом запусков необходимо отслеживать изменения не только при первом запуске сверх установленного лимита, но и при последнем «законном» запуске.
  5. По возможности, необходимо выяснить, как программа реагирует на попытки обойти ограничение времени использования при помощи изменения системной даты. Если программа успешно «проглатывает» любое изменение даты, высока вероятность того, что защита реализована максимально простым способом, и обойти ее будет нетрудно. Разумеется, все эти действия должны сопровождаться созданием снимков системы: если программа обнаружит Ваши манипуляции с системным временем, она может отреагировать на это созданием «метки», свидетельствующей о попытке обойти защиту.
  6. Проанализировать все собранные данные, выявить подозрительные ключи реестра и другие объекты, которые могли бы использоваться для хранения защитных данных, и затем на основе собранной информации попытаться восстановить работоспособность программы, последовательно удаляя или восстанавливая первоначальные значения подозрительных ключей.
  7. Проверить, нет ли в программе неочевидных ограничений по времени использования. Нередко встречаются программы (как правило, это демонстрационные, пробные и бета-версии), в которых помимо обычного ограничения на время пользования имеется дополнительная проверка на максимальную дату запуска, после которой программа считается устаревшей и перестает работать.


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


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


Глава 4.

Переменные и константы.


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


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


Приблизительный рейтинг изменений по степени их влияния на логику программы (от минимального к максимальному) выглядит следующим образом:


- Модификация константы

- Изменение значения предварительно инициализированной переменной

- Изменение условия перехода на противоположное

- Удаление линейной, то есть не содержащей ветвлений и вызовов нетривиальных подпрограмм, последовательности команд

- Дописывание в программу собственного кода; изменение значения, возвращаемого функцией

- Модификация внедренного в программу ресурса, важного для логики программы (т.е. такого, изменение которого меняет поведение программы)

- Удаление из программы логического блока (например, вызова нетривиальной функции); удаление внедренного в программу ресурса


Этот принцип, как любое другое широкое обобщение, требует достаточно осторожного отношения и в некоторых случаях может повести Вас по неоптимальному пути. Но на практике принцип минимального вмешательства обычно работает вполне успешно. Да и с точки зрения простого здравого смысла очевидно, что найти и исправить условие, которое проверяет зарегистрированность программы – прием куда более безобидный и надежный, чем вырывание с корнем nag-screen’ов и триальных ограничений при помощи глубокого патчинга.


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


Наверное, наиболее распространенным типом данных, используемым в программах, в настоящее время являются целые числа. Абсолютное большинство существующих процессоров, в том числе и наши любимые x86, изначально ориентировались на работу с целыми числами определенной разрядности (в настоящее время на платформе x86 наиболее актуальны 32-битные целые со знаком или без). Именно целые числа являются наиболее естественных форматом для хранения всевозможных счетчиков и контрольных сумм. Однако возможны и более неординарные применения: в тридцати двух битах можно успешно хранить числа с фиксированной точкой и даже логические значения. Когда я переходил от программирования под ДОС к программированию под Win32, меня сильно удивляла расточительность фирмы Microsoft, отводившей под простую булевскую переменную целых 4 байта, и только более глубокое изучение архитектуры 32-разрядных процессоров и ассемблера расставило все по своим местам.


Что Вы будете делать, если Вам потребуется найти в программе сравнение чего-либо (например, регистра или содержимого ячейки памяти) с определенной целочисленной константой? Для некоторых целых чисел достаточно обычного поиска блока двоичных данных в файле. Однако двоичный поиск далеко не со всеми числами работает одинаково хорошо: если Вы ищете число 3B9ACA00h, вероятность ложного срабатывания будет весьма небольшой, но вот если Вы попытаетесь найти в исполняемом файле число 10 или 15, то, скорее всего, просто устанете нажимать на кнопку «Найти далее». Если вспомнить, что числа 10 и 15 могут храниться не только как 32-битные, но и как одно- и двухбайтные, становится ясно, что двоичный поиск небольших чисел в исполняемых файлах – далеко не самая лучшая идея. Кроме того, при таком способе поиска никак не учитывается структура исполняемого файла программы, поскольку Вы ищете нужную Вам константу не только в коде программы, но и в PE-заголовке, секции данных, секции ресурсов и прочих областях, имеющих к коду программы самое отдаленное отношение. Хотя в принципе эта проблема, конечно, решаема: нужно лишь ограничить область поиска секциями, содержащими данные и код.


Однако есть и другой, более эффективный метод поиска в программе известных заранее значений. Как ни странно, но в реальных программах широко используется лишь сравнительно небольшой набор целочисленных констант: это, прежде всего, небольшие положительные числа от 0 до 7 (а также небольшие отрицательные от -3 до -1) и степени двойки: 8, 16, 32 и т.д. Другие константы в программах встречаются значительно реже. Попробуйте сами провести эксперимент – дизассемблируйте какую-нибудь достаточно большую программу и найти в ней какое-нибудь сравнительно небольшое число, например, 32h, которое в этой программе заведомо имеется. Для этого эксперимента я написал простейшую программку на Delphi 7, вся функциональность которой концентрировался в следующем коде, имитирующем простейшее ограничение на число строк в документе:


procedure TForm1.Button1Click(Sender: TObject);

begin

if Memo1.Lines.Count>50 then

begin

Application.MessageBox('More than 50 items not available','Demo version');

Close;

end

else Memo1.Lines.Add(Edit1.Text);

end;


В результате компиляции этот весьма нехитрый текст превратился в исполняемый файл размером более 350 килобайт (я намеренно создал проект с визуальными компонентами, а также использовал режим компиляции без runtime packages, чтобы мой собственный код составлял в исполняемом файле очень малую долю по сравнению с библиотеками Delphi). Затем я дизассемблировал откомпилированную программу при помощи W32Dasm и получил листинг текст длиной более 180 000 строк. Казалось бы, обнаружить область, где происходит сравнение с числом 50 в этом листинге ничуть не проще, чем найти иголку в стоге сена. Но я воспользовался функцией поиска в тексте строки, в качестве параметра поиска указав 00000032 (так в W32Dasm отображается число 50; заодно это позволило отсеять команды вроде mov eax,[ebx+32h], обычно использующиеся для доступа к элементам массивов и полям структур). Реальность превзошла самые смелые ожидания: четырехбайтное число 32h встретилось в листинге всего два (!!!) раза:


:004478C6 6A32 push 00000032

и

:004505BB 83F832 cmp eax, 00000032


Чтобы догадаться, что нужное нам сравнение выполняется во второй строке, достаточно самых минимальных познаний в ассемблере. Из этого следует вывод: поиск нужной константы в дизассемблированной программе вполне реален, даже несмотря на огромный объем листинга.


Далее: Вам наверняка интересен не сам факт наличия константы где-то в недрах кода, а то, в каком контексте эта постоянная используется. Иными словами, если Вы знаете, что программа сравнивает число записей в базе данных с некоторым значением (в нашем случае - 32h), то среди всех строк, в которых присутствует эта константа, в первую очередь следует рассматривать команды сравнения (cmp) и вычисления разности (sub и sbc). Хотя нельзя забывать о существовании менее очевидных способов сравнения, например, таких:


mov ebx,50

cmp eax, ebx


или


push 50

push eax

call Compare2dwords


Ну и, раз уж речь зашла о сравнениях, нельзя не упомянуть об альтернативных вариантах реализации этой, казалось бы, нехитрой операции. Поразмыслим над приведенным выше примером сравнения содержимого регистра eax с числом 50. В самом деле, условия eax>50 и eax>=51 в приложении к целым числам имеют один и тот же смысл, а код


cmp eax,50

jg my_label


работает совершенно аналогично коду


cmp eax,51

jge my_label


Если необходимо выяснить, больше ли содержимое регистра EAX, чем 31, или нет, то проверка может выглядеть даже так:


and eax, 0FFFFFFE0h

jnz my_label


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


if my_var=[число] then <переменная_равна_числу>

else if my_var>[число] then <переменная_больше_числа>

else <переменная_меньше_числа>


На ассемблере эта конструкция тоже реализуется в три строчки, однако операция сравнения здесь требуется всего одна:


cmp my_var,[число]

jz is_equal

jg is_more_than


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


Если рассматривать области возможного практического применения вышеописанного приема, то лучше всего поиск известной константы в дизассемблированном тексте работает на триальных ограничениях типа «не допускается создание больше N элементов в базе данных». Как правило, N больше 7 и является целым числом, что облегчает поиск нужной константы. Исходя из принципа минимального вмешательства, для обезвреживания таких ограничений я предпочитаю исправлять не команды сравнения, а сами константы. Действительно, если программа для проектирования интерьера комнаты не способна работать более, чем с 20 объектами, для практического применения она вряд ли будет пригодна. Но вот та же программа, где максимальное количество обрабатываемых объектов увеличено до двух с хвостиком миллиардов наверняка удовлетворит даже самого взыскательного пользователя.


Одним из наиболее частых вопросов, возникающих у начинающего крэкера, звучит так: «Программа работает 30 дней, но я так и не нашел в листинге сравнения с числом 30. Что делать?». Один из факторов я уже описал выше – там могло быть сравнение не с числом 30, а с числом 31. Однако этим список возможных причин неудачи не исчерпывается. Как мы все знаем, день состоит из 24 часов, каждый из которых состоит из 60 минут, каждая из которых состоит из 60 секунд. Более того, продолжительность секунд также может измеряться во всевозможных «условных единицах», например, в миллисекундах. Тысячные доли секунд, в частности, используются в таких функциях WinAPI, как SetTimer (таймеры Windows часто используются для установки ограничений на продолжительность одного сеанса работы с программой) или Sleep. А вот в функциях, возвращающих время в виде структуры типа FILETIME, используются уже другие «условные единицы», равные ста наносекундам. Так что пресловутые 30 дней – это не только 30 дней, но еще и 720 часов, 43200 минут, 2592000 секунд, ну и так далее. И каждое из этих значений может быть использовано в программе как один из аргументов операции сравнения. Надо отметить, что в «условных единицах» может быть представлено не только время, но и многие другие величины: масса, географические координаты, денежные единицы и т.д.


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