Безопасность в Дельфи

       

Послесловие


Раздел "Анти крэковые мучения" Дмитрий Логинов , дата публикации 25.04.00
ПРИВЕТ, КОЛЛЕГИ.
После небольшой статьи на тему "Защита программ" ко мне приходят письма. Поэтому я хотел бы организовать раздел на "Площади", где вы меня можете хаять. Или просто сами что-то уточнить. Все решить Королеве.

А вот у нас уже есть нужный раздел. Так давайте в нем и обоснуемся.
Хаять лучше не надо. Это неинтересно... Лучше уточняйте, дополняйте, спрашивайте, предлагаейте свои варианты :о).
Лена Филиппова

Но прежде, хочу привести данные "из неопубликованного" :
"ИЗ НЕОПУБЛИКОВАННОГО"
1) Я получил справедливое замечание, что не привел самого распространенного и проверенного метода, хотя его описывают во всех учебниках. Исправлюсь. Значица так ;)
Метод сей имеет вирусное происхождение. Сейчас каждый третий вирь использует механизм морфизма. Суть его стоит в том, что само тело виря криптуется каким-нить ключем. Обычно это банальный ксор по-байтно. Если этот ключ не хранится в проге, а берется извне (BIOS, Порт или ДСЧ) - то пролетают почти все эвристики. А к началу виря цепляется декодер. Этот декодер должен менятся. Сами понимаете вариаций на тему ксор в цикле может быть бесконечно много (если вставлять незначащий код). Умные люди посмотрели на такие дела и радостно потерли ручки. Что их обрадовало?
Из предыдущих статей или своего опыта вы знаете, что вся нудность в разборке кода ломаемой проги сводится к правке одно-двух байт (условный переход и move какого-нить флага). В зависимости от кол-ва проверок объем правки растет. Как следить за целостностью? Ведь проверка на CRC - это тоже условный переход. Вот тут и пришли на выручку вирмейкеры. Если тело проги криптануть в одностороннем порядке (RSA,метод эллипсов или просто перебор), то править байты будет не так тривиально, как прежде.
Для начала надо найти декодер, чтобы узнать что за шифр. Второе,надо раскодировать ломаемый файл. И третье, надо заместить вызов декодера на вызов пустышки. Можно, конечно, слить два последних этапа в один. Например, если ключ на раскриптовку берется из порта, то можно просто взять на один день у знакомого ключ, скачать ключ оттуда и написать VXD, которая подставляет ключ.Теперь заглушку можно выбрасывать! Но провернуть такое может 1 из 10 кракеров. Это хорошие профессионалы. Их вообще мало что остановит, даже малые размер гонорара. Парни до самозабвения протирают глаза и штаны просто для того, чтобы "сделать" защиту. Они сродни вирмейкерам.
"Я дерусь, потому что я дерусь" - как говаривал душка Партос.
Так что, используйте этот метод. Один недостаток - он очень сложен в реализации. Ниже я предложу простую реализацию такой защиты на целостность. Будьте терпеливы ;) 2) Недавно я столкнулся с защитой программы программирования сотовых телефонов NOKIA. Все там к у людей. И криптованные файлы. И заглушка в порту. Одна приятная тонкость. Команды в программатор послаются, как импульсы. Т.е. время установки какого-нить бита. В результате, отладка софтайсом становится просто невозможной. Т.к. обработки неправильных команд в программаторе не было. И любая длительность сигнала ложилась во флеш. Привел я этот случай для того, чтобы показать, что возможна не только привязка к аппаратуре, но и к быстродействию. Недостаток - вы сами не сможете отлаживаться. И в многозадачных системах быстродействие величина плавающая. Но для эрудиции и будущей крутости этот метод нужно иметь в загашниках. 3) Если вы пишете на Борландовских продуктах, вы постоянно имеете дело с DFM. Это внутренний формат Борландовского ресурса для DESIGN TIME режима программы. При линковке эти файлы транслируются в виндовый ресурс и аттачатся к ехе-шнику.
Конечно, если у вас не стоит использование "build with runtime packages" на закладке PACKAGES. Тогда используются DCL (в смысле DLL), которые обитают в Windows\System каталоге. Тогда ваш ехе-шник весит от силы 100 килограмм, не могет запускаться без этих модулей, а ярые противники Борланда начинают орать, что Delphi интерпретатор. О чем это я ?
А, так вот. Когда вы пишете обработчик события, в dfm прописывается его имечко, а при линковке перед ним лежит смещение кода вашего обработчика. Просто лафа для кракера - он без всяких отладчиков найдет по смыслу названия нужный обработчик и сделает свое черное дело. Вывод: не называть свойства, методы и обработчики, отвечающие за безопасность понятными именами, типа CheckPassword. Также, старайтесь эти обработчики присваивать неявным путем (вешаясь на WM_CREATE). Не вставляйте проверку в один обработчик с кодом чтения пароля/ключа. И наконец, еще один метод.
ПРИМЕР ЗАЩИТЫ ЦЕЛОСТНОСТИ КОДА
Чтобы понять данный метод нужно хорошо себе представить конструирование ваших визуальных компонент. Это просто. Формат DFM файла очень прост. Он имеет заголовок длиной 80 байт. Начинается байтами FFН, 0АН и 00, далее имя ресурса (грубо говоря имя окна), далее байты 00, 30Н, 10Н. После четыре байта под адрес, потом сигнатура "TPF0". Дальнейшее представляет собой открытую книгу, поэтому не заслуживает нашего внимания. Напомню также следующую иерархию: TFileStream <- THandleStream <- TStream. Далее, не знаю как в делфях, но в буилдере у формы несколько конструкторов. Обычный в дизайновом режиме читает все published и events из DFM, туда же их и пишет. Но т.к. в этом есть необходимость только при дизайне, то классы TForm и TStream неассоциированы друг с другом. И другой конструктор, который имеет добавочный параметер int dummy. Если он не нулевой, то форма создается программно, без ресурсов и DFM. Это чистый регион. Даже не присвоены обработчики событий. Для чего это нужно? Form2 = new TForm(Application,1); // извиняйте что на сях ReadComponentResFile("Temp.dfm", Form2); // читаем что-то Строим карту вызова ReadComponentResFile -> TFileStream.ReadComponentRes(Instance) -> TStream.ReadComponentRes -> TStream.ReadComponent -> Reader.ReadRootComponent(Instance) ! Это сильно упрощенная последовательность. Там еще есть масса полезностей в том числе и ReadResHeader;
Все вроде ясно как божий день:
ReadComponentResFile использует проассоциированный с потоком TReader, а
WriteComponentResFile использует проассоциированный с потоком TWriter.
Далее TReader и TWriter пользуются Read и Write соответсвенно, а те в свою очередь вызывают ReadBuffer И WriteBuffer. Те, наконец-то, вызывают виртуальные Read и Write ассоциированных потоков.
Т.о мы приходим к простой схеме защиты целостности криптованием. Даже в нескольких схожих вариантах.
Рассмотрим один из них.
  1. Создается невизуальный компонент TCryptFileStream, у которого перекрыты Write и Read и в них вызывается процедура Crypt (кодер-декодер);
  2. Переписывается 2 процедурки ReadComponentResFile И WriteComponentResFile. В оных вызывается соответсвующие методы TCryptFileStream, если параметром указан файл с расширением DFC. Или имеющих другую сигнатуру в заголовке. В остальном все по прежнему.
  3. Пишется отдельная прога по шифровке существующих DFM.
  4. Процедура Crypt использует в качестве ключа контрольную сумму кода программы.
  5. Пишется проект, но перед самым окончанием, а вернее после него криптуются все DFM и BuildAll.
  6. Вуаля.
Другой способ - это написание TCryptForm, которая схожими методами прячет свой ресурс. Здесь пусть уж ваша фантазия разырается всласть. Пока все. С удовольствием отвечу на вопросы или прочту замечания.

Успешных кодировок,

Дмитрий Логинов




Содержание    Вперед