Автор:
Роман Ройтер
Я решил
написать статью о том, как защитить свои программы от взлома. Оговорюсь сразу,
практически невозможно создать такую защиту, которая могла бы противостоять
опытному взломщику. Но можно попытаться создать такую защиту, которая окажется
не по зубам около 90% взломщиков. Все мои идеи по защите основаны на личных
наблюдениях, и они могут не быть достоверными, но могут быть полезными против
большинства новичков и продвинутых взломщиков. Сейчас я расскажу, какие
инструменты использует средний взломщик:
Во-первых,
самый незаменимый инструмент это дебагер, то есть программа, которая позволяет
трассировать код чужой программы в живую. Список этих программ варьируется от
простого досовского debug'а до продвинутого Soft-Ice'а.
Во-вторых,
программа дизассемблер, которая превращает байты программы в команды
ассемблера, тем самым позволяет просмотреть, где и как действует защита. Причем
хороший дизассемблер показывает еще и где, какие строки используются в
программе. Далее идут всевозможные утилиты, для мониторинга реестра, файлов,
определители компилятора и упаковщика, распаковщики, дамперы памяти, генераторы
кряков и т.д. Ну и конечно самое главное это мозги взломщика, ведь во многом
результат взлома зависит от его сообразительности или идиотизма.
Короче наша
задача, обмануть все эти средства, и самое главное запутать взломщика. И вот
как мы это сделаем, я буду идти по приведенному списку и буду объяснять, как
защитится от этих средств. Начнем с дебагеров. Вообще-то, многие взломщики
недолюбливают VB, потому что он работает благодаря своим библиотекам, и
естественно взломщику потребуется очень много времени, чтобы разобраться, где
код программы, а где вызовы библиотечных функций (проверено на опыте). Но это
случается, только тогда когда программа была скомпилирована в P-код, то есть в
псевдокод. Значит первое кольцо защиты, должно быть компилирование в псевдокод.
Если вы думаете, что на этом все закончилось, то глубоко ошибаетесь. На свете
есть дебагер именуемый P-Code Loader, и он позволяет фильтровать вызовы
библиотек и показывать непосредственно код программы. Конечно, не у всех он
есть, но если и нет, то не стоит слишком надеяться на то, что взломщик
откажется от взлома. Поэтому P-код не должен быть главной ставкой программиста.
Несмотря на то что с псевдокодом придется таскать кучу библиотек все же не
стоит недооценивать этот вариант.
Иногда
программы используют различные приемы против дебагеров, основанные на их
свойстве выполнять команды до их фактического выполнения. То есть теоретически
можно создать команду, которая отправит дебагер в тартарары, в то время как
процессор проигнорирует эту команду из-за системы прогнозирования переходов. Я
не умею это делать, поэтому упоминаю это только потому, что однажды встретился
с этим приемом. Кстати после первой версии (ох, это програмирование!) статьи
прислали (к сожалению я потерял это письмо) мне пример обнаружения Soft-Ice’а с помощью
ассемблера, но автор не учел того, что в обычном VB нет возможности использовать
инструкции ассемблера, поэтому он бесполезен.
Копаясь с
АПИ справочнике, я обнаружил функцию IsDebuggerPresent, которая возвращает
True, если в системе включен системный дебагер. К сожалению эта функция не
работает на софт-айсе и подобных ему.
Функция
работает, начиная с NT и Win98:
Declare Function IsDebuggerPresent Lib "kernel32" () As Long
Далее,
дизассемблеры. В инструментарии взломщика дисассемблер занимает почетное место.
Редко взломщик изучает программу без дизассемблера. Дизассемблер дает не только
информацию о коде программы, но и показывает имена используемых функций и
строковые выражения, использующиеся в программе, а также предоставляет удобную
навигацию по вызовам функций и прыжкам. Дизассемблер можно обмануть несколькими
путями. С начала, что значит обмануть? Обмануть значить не дать ему показать
истинный код программы. Этого достигнуть можно следующим способом, программу
можно упаковать каким-нибудь упаковщиком типа Aspack, UPX и т.п.
В этом
случае дизассемблер сможет показать разве что код упаковщика и более ничего. Но
ведь существуют и распаковщики, поэтому всегда есть вероятность того, что у
взломщика найдется подходящий распаковщик, и он получит готовенький код вашей
программы. Что бы предотвратить это, нет четко определенных способов. Разве что
пользоваться несколькими упаковщиками или использовать нестандартный, мало кому
известный упаковщик. Это серьезно затруднит взломщиков, но особенно настырных
это не сможет остановить (ведь теоретически можно найти то место где упаковщик
передает программе управление и провести кое-какие манипуляции с кодом).
Кроме того,
есть ещё один способ усложнить взломщику жизнь. Для этого я расскажу, как
производится обычный взлом с помощью дизассемблера. Берется жертва,
дизассемблируется. Первое на что смотрит взломщик, это какие строки использует
программа. Допустим, программа при вводе неверного серийного номера показывает
что-то типа "Сорри, но ваш серийник не подходит нам". Взломщик первым
делом ищет эту строчку, и если находит, то у него уже появляется точка старта
взлома, так как он сможет в большинстве случаев найти, то место где вызывается
это сообщение, и соответственно найти место проверки серийника. Значит, наша
задача убрать эту возможность для взломщика, то есть убрать все важные сообщения
из программы. Это можно сделать, если вынести все сообщения во внешний файл и
читать их оттуда во время рантайма. В этом случае дизассемблер покажет разве
что имя вызываемого файла. Особенно хорошо загрузить сообщения в начале
программы, чтобы взломщик не смог связать их с каким-нибудь важным местом в
программе. Ну и я думаю понятно, что самая глупая ошибка это писать прямо в
программе правильный пароль. Это сводит на нет всю защиту, так как очень
просто, даже с помощью блокнота просмотреть файл и найти характерные строчки.
Есть ещё
один способ повысить безопасность программы, правда я не знаю, как это сделать
на VB, но смысл состоит в следующем: надеюсь всем известно о компьютерных
регистрах. Также вам известно, что на ассемблере можно вызвать процедуру по
адресу находящемуся в регистре. Так вот, дизассемблер не может сказать какое
значение будет в регистре в тот или иной момент (потому что он генерирует
«мертвый код», т.е. шаблон кода без данных), поэтому можно вызвать окно с
сообщением через регистр, и тогда ни один дизассемблер не сможет сказать,
откуда вызвана та или иная функция. Правда, сейчас, когда я думаю над этим, я
вспомнил что в функцию можно передавать параметры ByRef, т.е. передать
указатель на значение а не само значение. Поэтому есть шанс что VB передаст указатель
через регистр. Но я не уверен, так что это может и не работать. Иногда я
замечал, что некоторые программы подвешивали дизассемблер при попытке дизассемблирования
файла. Я не знаю, как это делают, но подозреваю, это происходит из-за
несущественно испорченной структуры файла. Как обмануть мониторы реестра и
файлов я не знаю, но могу посоветовать спрятать иголку в стогу сена (опять же
встречались случаи падения мониторов при запуске некоторых программ). То есть
запрятать чтение данных пользователя в кучу ненужных вызовов. Также желательно
не давать очевидных имен типа "Code" или "User Name".
Вообще чтение данных, является слабым местом любой защиты, так как большинство
программистов полагают раз прочтенные данные правильные, то значит, все в
порядке и бросают все свои силы на защиту процедуры подсчитывания пароля,
забывая о том важном месте, где программа устанавливает, куплена она или нет.
Поэтому настоятельно советую не забывать ни об одном месте, где программа
делает решающий выбор. Кстати подавляющее число программ использует одну и туже
функцию для этой цели. Это оказалось фатальным для этих программ. Нельзя,
повторяю ни в коем случае нельзя пользоваться одной и той же функцией. Её легко
вычислить и тогда она теряет свой смысл. Помните самая хорошая защита, это
защита которую нельзя обнаружить. Желательно пользоваться разными функциями,
причем желательно рабочими (чтобы их корректировка привела к
неработоспособности программы). Также лучше пользоваться разными переменными,
чтобы нельзя было отследить адрес в памяти. Далее сверяйте переменные, и если
они не равны, то лучше программе аварийно выйти, чем дать взломщику продолжить
работу.
Поставьте
таймер, который проверяет каждые 10 минут право пользователя на работу. Причем
если вы используете чистые проверки (без мусора), то взломщик может заметить
это, и использовать это. Поэтому проверяйте пользователя в месте с мусором,
чтобы ваша проверка не бросалась в глаза. Кстати тот же таймер (или другой с
меньшим интервалом) подскажет вам, трассируют ли вашу программу или нет. Тут
есть одна проблема, дело в том что VB’ый таймер обращается к системному, т.е.
если системный замораживается, то и защита не узнает что произошло. С другой
стороны аппаратный таймер продолжает работать нормально, т.е. надо читать
аппаратный таймер, а не системный. Win98 позволяет читать данные из биоса, а NT не даст этого
сделать по соображениям безопасности, так что я не обладаю нужной информацией
на эту тему. Поэтому весь этот абзац является плодом моего воображения.
Кстати есть
смысл хранить данные зашифрованными в реестре или файле, чтобы взломщик не
сразу вычислил, где вы храните их. И конечно очень глупо записать где-нибудь
строчку типа Registered=0, я думаю, что даже младенец поймет, что надо сделать.
Но это можно использовать это как прикрытие, то есть если Registered=1, то
можно показать сообщение типа <Попался грязный хакер!!!> и форматнуть ему
комп (шутка). Но, в самом деле, идея не плохая. Вообще все чем я вам пудрю
мозги, рассчитано на то, чтобы запутать взломщика.
Но это были
цветочки, теперь ягодки.
Как вам
известно, проверка пароля самая важная в защите, так как именно ей приходится
выдерживать удары тяжелой артиллерии и поэтому у нее должна быть защита, как у
банковского сейфа.
Для начала я
объясню, как взломщик начинает взлом. У него обычно есть два выбора начать с
начала или с конца. Это значит, что он может начать исследовать программу с
самого ввода пароля или начать с того момента, когда программа показывает
сообщение о неверном пароле. У обоих методов есть свои плюсы и минусы. Если
идти с начала, то можно узнать, где хранится введенное имя и пароль, и что с
ним делают. Но с другой стороны приходится фильтровать кучу мусора, чтобы найти
что-то стоящее. Если идти с конца, то есть шанс посмотреть код вышестоящий
вызова сообщения или если это отдельная процедура выйти к месту проверки. Но и
здесь есть свои трудности, иногда программа не использует стандартные функции
показа сообщений, поэтому её нельзя поймать, или после показа сообщения
программа не возвращается туда, откуда оно вызывалось, и поэтому тоже не
узнаешь, что там случилось. Наша задача сделать так чтобы взломщик не понял где
вообще проверка и не смог её подсмотреть. Так как в большинстве случаев
взломщики идут первым путем, то и мы пойдем их путем. Я не вижу способа
прочитать имя и пароль без вызова hmemcpy, эта стандартная функция Windows
которая вызывается всюду без нашего разрешения, так что здесь у взломщика есть
преимущество. Но кто сказал, что нам обязательно проверять пароль, тут же не
отходя от кассы. Мы можем сказать <так и так учтем вашу просьбу, подождите
ответа> и спокойно записать данные где-нибудь, и тут же использовать
вышеописанный таймер и если нас просматривали, то выходим без лишних сообщений.
Если нет, то, где-нибудь через 20 кликов мышки, проверить тихо мирно чего нам
подсунули. Когда я перечитал то что написал, то у меня появилась идея: можно
подсунуть взломщику фальшивую функцию проверки и запутать его в конец.
Желательно
закодировать и имя и пароль по какому-нибудь крепкому алгоритму и сверить
результаты. Причем сверять надо по букве, чередуя с мусором, а то программа
типа Smartcheck запишет <сверялись такие и такие символы> и если их не
спрятать, то можно догадаться, где это творится. Кстати если результат не верный
то не надо ругаться, можно спокойно продолжать работу, так как взломщик может
подловить на этом сообщении программу. И как я уже говорил, используйте разные
функции всюду.
Кстати, я
недавно прочитал книжку по хакингу, крэкингу, и фрикингу. Так вот там автор
утверждал, что надежность защиты системы определяется надежностью её самого
слабого звена. Это утверждение применимо и в нашем деле. Насколько я понимаю
самая слабая точка в защите, это то место где программа решает как её вести
себя дальше, то есть купили её или нет. Это является очень критической точкой,
так как большинство начинающих и ленивых взломщиков, часто трассируют огромные
куски кода ради вышеописанного места в программе. Значит надо внимательно
следить за этим, и лучше завести несколько дублей переменной и сверять их.
Иногда
программы используют CRC, то есть число уникальное для каждой программы которое
определяет её целостность. Его можно хранить где-нибудь и подсчитывать заново
каждый раз, и если числа не совпадают, то или на компе вирус или взломщик
копался в коде программы. Если копался то лучше выйти. И ещё лучше выйти без
сообщений. Я советую вообще воздержаться от каких либо сообщений по поводу
защиты. А все потому что нельзя давать взломщику хоть малейший шанс подловить
программу на чём либо. Функций для показа сообщений не так уж и много и они
довольно известны, а вот функция, которая используется для выхода программы, не
очень известна, и есть шанс, что взломщик не поймет что делать.
Я перечитал
то что написал и понял что так и не объяснил что, значит, подловить программу.
Сейчас объясню. Программы бывают большими, и защита бывает тоже не маленькая.
Поэтому чтобы не ворочать зря горы, взломщики используют точки останова. То
есть в VB допустим, чтобы показать сообщение msgbox используется функция
rtcMsgBox, взломщик может сказать дебагеру (если знает эту функцию), останови
программу, если она вызовет rtcMsgBox. И когда ваша программа возмутится из-за
неверного пароля, взломщик сможет начать свое исследование гораздо ближе к
важному месту защиты, чем надо. Поэтому я считаю, что лучше всего вообще не
давать взломщику лишнего шанса, проникнуть в программу.
Часто
взломщики не просто банально меняют нужный код, а исследуют алгоритм создания
пароля и пишут генераторы серийных номеров. Поэтому необходимо хорошо защитить
этот алгоритм. Желательно использовать какой-нибудь криптостойкий алгоритм типа
RSA или MD5. В Интернете можно найти методы работы с ними. Также, чтобы
затруднить взломщика желательно разбить генерирование пароля на несколько этапов,
разбить по функциям (желательно рабочим, чтобы чаще вызывались не только из-за
пароля), и в промежутках между вызовами этих функций проверять на исследование
вашу программу. И ещё помните что когда ваша программа под дебагером, она не
может выполнять параллельно несколько вещей, то есть если она проверит на
наличие дебагера, взломщик может это заметить и выключить. Поэтому не давайте
никому этого шанса, относитесь к каждому пользователю как к потенциальному
взломщику.
Ну вот и
все, что я хотел рассказать.
Ну, а теперь
некоторые критики, наверняка зададуться вопросом: а на фига собственно этот
Роман написал эту статейку?
Так вот, я
отвечаю вам на этот исключительно важный вопрос:
- Мне захотелось поделиться с людьми своими знаниями.
- Мне нечего было делать.
- Мне надоело смотреть, как все кому не лень издеваются
над программистами, которые используют схемы защит 20-летней давности, и я
решил по мере своих сил исправить положение.
- Я заметил, что когда объясняешь кому-нибудь, что-то,
то начинаешь сам лучше понимать.
Все
приведенные методы используйте, как хотите. Ну и конечно тому, кому лень
защищать свое детище, или он отмахивается тем, что программу все равно сломают,
не стоило даже и читать эту статью.
Я буду рад
любым комментариям, замечаниям и любым отзывам. Вы можете прислать их мне на noobsaibott@hotmail.com.