Представим, что вы потратили кучу времени и ресурсов, разрабатывая последние несколько месяцев неплохую программу, и даже отчего-то имеете версию для Linux`а. Видимо, вы хотите окупить затраченные усилия — проще говоря, заработать на своей программе кучу денег. И если вы не опубликовали свой исходный код, в надежде заработать другими способами, то необходимо как-то защитить вашу программу от несанкционированного копирования. Т.е. программа должна работать только у того, кто ее купил, и не должна работать (или работать неправильно, или, что бывает чаще всего, иметь функциональные ограничения) у халявщиков и крякеров. Проще говоря, чтобы ваш уникальный алгоритм не смогли своровать. Если разработке/снятию защит под DOS/Windows посвящено множество сайтов, то практически невозможно найти ни одной работы, посвященной тому же самому, но под Linux. Между тем уже имеется множество коммерческих программ под эту, в общем, не самую плохую из распространенных, OS. С прискорбием заметим, что вся их “защита” составляет максимум 1% от их Windows аналогов и снимается в худшем случае за пару часов. Постараемся объяснить, почему происходит именно так, почему под Linux`ом не работают многие традиционно используемые для защиты Windows-программ способы, и, возможно, после прочтения этой главы вы сможете придумать что-то действительно эффективное. Итак, в чем же главное отличие Linux от прочих OS? В доступности исходного кода всего (ну, кроме, разве что, вашей программы, стоящей мегабаксы), что работает (или не работает) на вашей машине. Это делает написание защиты под Linux просто кошмаром — вы не можете быть уверены, что функция strcmp из стандартной run-time library — это действительно strcmp, а не ее измененный (обычно не в вашу пользу) эмулятор. Вы не можете доверять ничему в такой операционной системе — вследствие доступности исходного кода любая ее часть может быть модифицирована крякером для взлома вашей программы, включая такие важнейшие компоненты, как ядро и run-time library. Ваша программа работает в самой агрессивной среде, какую только можно себе представить. В самом деле, если бы вы могли изменить в Windows 9x, скажем, kernel32.dll (имеется в виду не ковыряние в машинном коде с помощью дизассемблера, хотя возможно использовать и такой метод — нет, просто редактирование исходного кода и последующая перекомпиляция)— разве было бы возможно существование защит вроде VBox? А пока для вас плохие новости — ваша программа обязательно будет сломана. Это на самом деле чисто экономический вопрос. Представим, что ваш программный продукт стоит 1000$. Среднемесячная зарплата неплохого программиста из нашей страны едва ли составляет 200$. Таким образом, если какой-нибудь парнишка из Сибири затратит на слом вашего творения меньше 5 месяцев — он будет экономически выгоден. Заметьте, что здесь ни слова не было сказано ни об операционной системе, под которой работает ваш шедевр, ни о сложности и стоимости использованной системы защиты. Что же делать — идти в монастырь (хотя в женский иногда наведываться — наверное, неплохая идея)? Вы должны рассматривать защиту своего программного продукта не как 100% средство от ваших головных болей, а всего лишь как средство, затрудняющее жизнь крякеру. Скажем, если ваша защита остановит 9 крякеров из 10 — это очень неплохой результат. Конечно, не все 9 остановленных купят вашу программу, но их будет явно больше, чем для случая, когда ваша защита сломана 9-ю из 10. Итак, довольно пустых разговоров. Для начала сделаем краткий обзор применяемых крякерами инструментов (хотя вполне возможно рассматривать Linux как один большой инструмент крякера). Отладчики GDB Отладчик userlevel mode. Загружает файлы с помощью BFD. Что-то вроде старого недоброго debug из DOSа. Также оформлен как библиотека. К нему есть множество интерфейсов, наиболее удобный для X Windows, IMHO, DDD (требует LessTif). Также заслуживает отдельного упоминания SmartGDB. Крайне интересная идея прибить сбоку отладчика script engine. Вещь очень любопытная с точки зрения автоматизации труда крякера — вы можете написать script, который посадить затем как триггер на точку останова. Ваш script смог бы, например, проверить переменные в отлаживаемой программе, и в зависимости от их значения, например, поставить новую точку останова (с новым scriptом), сбросить кусок памяти в файл, сварить какаву... Kernal level debugger от все той же SGI. Пока крайне сырая вещь (и требует нестабильной версии кернела), но может служить прототипом для более пригодных к использованию изделий. Дизассемблеры Без сомнения, IDA Pro. Также иногда можно на скорую руку использовать Biew, objdump (или даже ndisasm, дизассемблер от Netwide Assembler), но это несерьезно. Более неизвестны инструменты, которые позволяют дописывать к ним новые процессорные модули, загрузчики для нестандартных форматов файлов, а также plugins, облегчающие автоматический/интерактивный анализ. strace или truss под UnixWare. Аналог regmon/filemon/BoundsChecker водном флаконе. Ядро Linux`а имеет поддержку перехвата системных вызовов (функция ptrace). Т.е. можно запустить любой процесс как подлежащий трассировке через ptrace, и вы сможете отследить все системные вызовы с их параметрами. Более того, после небольшой модификации эта функция (которая имеет доступ к виртуальной памяти трассируемого процесса) может быть использована, например, для run-time patching, внедрения кода в адресное пространство любого процесса, и так далее, и тому подобное. ptrace Менее надежное, но более простое в реализации средство. Можно вызвать ptrace для самого себя. Если вызов был неудачен — значит, нас уже трассируют, нужно сделать что-нибудь по этому поводу (sys_exit, например). Впрочем, ведь если нас уже трассируют, ничего не стоит перехватить данный вызов и вернуть все что угодно... Memory dumpers Файловая система /proc может использоваться для таких целей. Файл maps используется как карта выделенной процессу виртуальной памяти, а файл mem является ее отображением, можно сделать в нем seek на нужный адрес и легко сохранить необходимый участок в файл или куда-вы-там-хотите. Простая программа на Perl размером 30 строчек может быть использована для снятия дампа памяти вашей драгоценной программы и сохранения ее в файл. Намного проще, чем под Win32. Шестнадцатеричные редакторы С этим тоже никогда не было больших проблем. Даже стандартный просмотровщик Midnight (или Mortal, его второе имя) Comanderа умеет редактировать в шестнадцатеричном представлении. Для гурманов рекомендуем Biew. Стандартные средства разработки Для модификации ядра/runtime библиотек (а также для создания patch`ей и keygen`ов) нужен как минимум компилятор “C”. Способы защиты Итак, надеемся, вы еще не уснули и не впали в депрессию. Что же можно противопоставить всему вышеописанному ? Явно годятся далеко не все методы, используемые в Win32. Тем не менее, представим несколько... Против BFD (GDB, objdump, strings и т.д.) BFD — это библиотека для манипуляций с бинарными файлами. По счастливому стечению обстоятельств она используется также отладчиком GDB. Но для ELF-формата (наиболее распространенный формат исполнимых файлов под всеми современными Unix`ами) она реализует неправильный алгоритм загрузки. Для этого можно использовать sstrip или ELF.compact (собственно, они делают одно и то же). Преимущества: Очень простой метод. Берете вашу готовую отлаженную программу и вместо команды strip запускаете elf_compact (или sstrip). Помимо всего прочего, вы уменьшите размер вашего дистрибутива. Хотя, глядя на размеры современных дистрибутивов Linux`а, этот аргумент кажется просто смешным... на этом преимущества заканчиваются и начинаются… Сплошные недостатки: Поскольку исходники BFD публично доступны, теоретически любой может доработать этот замечательный пакет, так что шансы снова будут не в вашу пользу. Собственно, вы должны помнить, что в Linux`е это относится ко всему. Но пока этот метод работает. Данный вариант может рассматриваться как очень простая защита от полных ламеров, rating 0.1%. Компрессия/шифрование кода программы Самый распространенный метод защиты под Win32. Под Linux`ом этот метод имеет некоторые особенности. Самое большое отличие в том, как разрешаются ссылки на внешние модули. Статическая линковка Не имеющий аналогов под Win32 метод. При сборке программы все используемые ею библиотеки просто статически линкуются в один большой толстый модуль. Т.е. для запуска такой программы ничего дополнительно делать не нужно — такая программа самодостаточна. Преимущества: Большую программу ломать труднее, чем маленькую. Независимость от среды исполнения. В самом деле, довольно часто возникают конфликты версий, установленных на машине конечного пользователя библиотек, из-за чего ваша программа может просто не работать. В случае статически слинкованной программы такие проблемы устраняются (а точнее, просто не возникают). Устранение (хотя и не 100%) возможности перехвата и эмуляции библиотечных вызовов. В такой программе вы будете несколько более уверены, что при вызове, скажем, strcmp будет вызвана ваша статически слинкованная функция strcmp, а не неизвестно что. Хотя в Linux никогда и ни в чем нельзя быть уверенным до конца. Недостатки: Увеличение размера программ. Если вы думаете, что таким образом сможете затруднить жизнь крякеру, вы глубоко ошибаетесь. С помощью технологии FLAIR, используемой в IDA Pro, а также применив инструмент RPat для создания сигнатур исходных библиотек (использованных вами при статической линковке), все ваши библиотечные функции могут быть легко опознаны. Более того, обычно не составляет большого труда выяснить, какие именно библиотеки используются, собрать их и сделать файлы сигнатур. Rating: 0.1%. Возможно, что кто-нибудь станет утверждать, что статическая линковка-де увеличивает необходимые ресурсы и что якобы разделяемые библиотеки разделяют сегмент кода между всеми процессами, использующими их. То же самое утверждает большинство учебников по Unix, но это не так. Самое простое доказательство — просмотр атрибутов сегментов памяти, занимаемых разделяемыми библиотеками (файл maps в файловой системе /proc). Вы почти никогда не увидите атрибута s(hared). Почему? Короткий ответ звучит так — из-за ELF. Дело в том, что при загрузке ELF-файла происходит настройка его перемещаемых адресов — relocations. При этом сегменту памяти (даже если это сегмент кода) присваиваются атрибуты Read/Write и если он при этом разделялся несколькими процессами, происходит копирование памяти при записи. Таким образом, разделение сегментов кода между процессами возможно только между родителем и его потомками (как результат функции fork). Эти же аргументы применимы и к утверждению, что якобы “упаковка кода программы приводит к увеличению ресурсов, необходимых для запуска такой программы”. Динамическая линковка Под Win32 все программы являются динамически слинкованными как минимум с системными .DLL, реализующими API. Более того, такая линковка осуществляется опять же самой системой с помощью все тех же функций API. Под Linux`ом все по-другому. Во-первых, программе совершенно не обязательно быть динамически слинкованной. Во-вторых, программа сама должна заботиться о загрузке всех необходимых модулей и динамическом разрешении ссылок. Это, правда, вовсе не означает, что вам каждый раз придется писать код загрузки модуля в память. Среда исполнения (а не kernal — как в Win32) предоставляет реализацию динамического загрузчика по умолчанию — в терминах Linux его называют ELF interpretor, или просто interpretor. При линковке в программу помещается информация, что для ее запуска необходимо после загрузки самой программы также загрузить interpretor и передать ему управление. На этом загрузка файла на исполнение с точки зрения кернела заканчивается. Но ваши головные боли только начинаются! Итак, чем плох стандартный ELF interpretor? Тем же, чем и весь Linux, — его исходники доступны, соответственно, он может быть легко изменен для достижения неизвестных вам целей. Он поддерживает уникальный для Unix`ов механизм предварительной загрузки. Рассмотрим его подробнее. Итак, предположим, что ваша программа импортирует функцию strcmp. Злобный крякер может написать собственную реализацию этой функции, создать объектный модуль и использовать ее вместо той, что вы ожидали! Для этого всего лишь нужно определить переменную среды LD_PRELOAD, чтобы она содержала список модулей, подлежащих загрузке перед модулями, импортируемыми вашей программой. Логика работы interpretor`а такова, что если некая импортируемая функция уже разрешена, то она больше не разрешается (в Linux импорт происходит по имени, а не по имени и имени библиотеки, как в Win32). Таким образом, можно штатными средствами внедрить в адресное пространство вашей программы все, что угодно, заменив при этом любую импортируемую функцию. Похоже на ночной кошмар, не так ли? Вы все еще думаете, что Linux написали не крякеры? С стандартным interpretor`ом есть и еще одна проблема. Дело втом, что его легко можно переписать для универсального инструмента, отслеживающего все вызовы импортируемых функций. Была идея встроить script engine в ELF interpretor, так что больше не нужно будет переписывать его под каждую конкретную программу, а всего лишь заменить script, который и сделает все, что нужно. Ведь ELF interpretor работает в адресном пространстве вашей программы, и он отвечает за начальную загрузку импортируемых функций, т.е. фактически такой script будет иметь над вашей программой полный контроль (под Win32 вообще неизвестны подобные инструменты. BoundsChecker, конечно, может отслеживать все вызовы импортируемых функций, но пока никому не пришла мысль дописать к нему script engine и использовать, например, для memory patching). Если после прочтения предыдущего абзаца вы все еще не выбросились из окна — есть для вас и хорошие мысли. Итак, что могут противопоставить авторы защиты? Собственный загрузчик в кернеле. Можно просто переписать стандартный загрузчик ELF-файлов (файл binfmt_elf.c из директории fs исходников ядра Linux), чтобы сделать жизнь крякера несколько тяжелее. Например, сбрасывать флаг трассировки процесса, иметь собственный формат исполнимых файлов (естественно, вместе с перекодировщиком обычных ELFов в этот формат), декриптовать/декомпрессировать куски файла перед загрузкой их в память и т.д. Недостатки: как ни странно, самым большим недостатком является необходимость иметь собственный код в kernel`е. Поскольку у конечного пользователя, что вполне естественно, время от времени возникает необходимость в пересборке кернела, вы должны будете предоставить либо объектный модуль (для насмерть прибитого гвоздями в кернел кода), или опять объектный модуль (для LKM — Linux Kernal Module). И то, и другое легко можно дизассемблировать/пропатчить, иистория повторяется... Как насчет смены версии кернела? Например, грядет смена ядра 2.2 на 2.4 — нужно будет иметь (и поддерживать!) как минимум код для обеих версий ядра... А если вы допустите ошибку? Если для userlevel-программ ваши ошибки, скорее всего, не смертельны, то ошибки в коде кернела могут иметь весьма плачевные последствия. Кроме того, отладка такого кода является сущим кошмаром, поверьте… Субъективная причина — сложность установки. Но при всех недостатках — это вполне приемлемый вариант. Content-Disposition: form-data; name="format" 1
|