Закрытые библиотеки
Общие сведения
Закрытые библиотеки позволяют скрыть от конечных пользователей часть исходных текстов прикладных проектов. При этом сохраняется возможность компиляции проектов, использующих закрытые библиотеки. Это позволяет пользователям изменять открытые части таких проектов без раскрытия закрытых частей.
Закрытие части исходных текстов позволяет решать задачи защиты авторского права и лицензирования прикладных компонентов. Для этой цели могут использоваться функции модуля ATHENA, обеспечивающие чтение информации из ключевого файла. См. подробнее Функции защиты прикладных блоков.
Закрытая библиотека представляет собой двоичный файл, содержащий обработанный и зашифрованный исходный текст оригинальной библиотеки. Закрытая библиотека должна иметь такое же имя файла как и открытая, но отличаться расширением – icl вместо ibl. В дистрибутив прикладных компонентов соответствующий файл ibl не включается.
Создание файла закрытой библиотеки происходит при компиляции проекта в среде разработки. См. подробнее Закрытые библиотеки в среде разработки.
Что следует поместить в закрытую библиотеку?
При вызове функций защиты, использующих сведения из ключевого файла, обычно принимается решение о невозможности выполнения некой операции или каких-то ограничениях. Ограничения могут опираться, например, на дату заносимого документа. Целью злоумышленника будет повлиять на принятие решения в пользу снятия ограничений. Достигается это исключением вызова функций защиты и возврат функциями проверки положительного решения.
В самом наивном случае в закрытой библиотеке содержатся только функции проверки защиты, а вызывают их открытые части кода. Злоумышленник может поступить двумя способами. Первый способ: удалить все использования закрытой библиотеки защиты, а в местах вызова ее функций поставить положительный результат (например, ДА или 01.01.2050). Второй способ: сделать библиотеку-двойник, содержащую заглушки всех используемых функций закрытой библиотеки (определяются по местам вызова в открытом коде). Функции-заглушки реализуются так, чтобы возвращался положительный результат проверки.
Приведенные соображения указывают, что код применения ограничений в прикладном компоненте также должен быть закрытым. Закрывать следует прикладной код, который а) не тривиальный; б) не требует доступа пользователя для настройки "под себя"; в) хорошо отлажен, т.е. не требует доступа пользователя для исправления ошибок. Если злоумышленник попытается исключить вызов таких прикладных функций или сделать их заглушки, то работа прикладного компонента нарушится. Например, закрытой прикладной функцией может быть расчет зарплаты, а ограничивающим параметром - максимальное количество работников (читается из ключевого файла).
При использовании функций проверки во многих прикладных компонентах их можно вынести в отдельную закрытую библиотеку, чтобы не дублировать код и не засорять прикладные библиотеки. Нетривиальные закрытые части прикладных компонентов будут использовать функции из этой библиотеки. При таком подходе возникает опасность создания злоумышленником общей библиотеки-двойника. Закрытые части прикладных компонентов могут ничего не подозревая вызывать функции-заглушки. Хотя непосредственные места вызовов функций общей библиотеки закрыты, но узнать имена и параметры функций можно в среде разработки, из сообщений об ошибках или просто угадав. Для пресечения создания злоумышленником фальшивого двойника общей библиотеки защиты, следует использовать секретные функции.
Секретные функции
Секретные функции позволяют предотвратить создание библиотек-двойников с набором функций-заглушек. Секретным является имя функции. Соответственно, вызов секретных функций должен осуществляться в закрытых библиотеках, доступ к исходным текстам которых есть только у разработчиков.
Имя секретной функции должно начинаться с префикса _h_ (от hidden - скрытый). Остаток имени может включать значащее имя и секретный код. Значащее имя помогает разработчикам понимать смысл выполняемой функцией работы, а секретный код - затрудняет злоумышленнику угадывание имени функции.
Пример секретной функции:
ФУНКЦИЯ ЛОГИКА: _h_ПроверитьГод_875344656385362723(ЦЕЛОЕ: год) // ... КОНЕЦ_ФУНКЦИИ
В примере префикс _h_ указывает компилятору, что функция является секретной. Значащая часть имени ПроверитьГод предназначена для чтения человеком. Секретный код представлен произвольной последовательностью цифр 875344656385362723.
Деление имени на значащую часть и секретный код – условное. Нет ограничений на их взаимное расположение и разделение символом подчеркивания. Секретный код может содержать не только цифры, но и буквы (например, GUID).
Обработка компилятором и виртуальной машиной И++ секретных и обычных функций отличается. В сообщениях об ошибках, отчетах профайлера и т.п. имена секретных функций отображаются в виде зведочек ***********. Особая обработка секретных функций происходит и при использовании отладчика в среде разработки. Эти меры не позволяют узнать имя секретной функции, кроме как из исходных текстов закрытых библиотек.
Пример использования закрытых библиотек
Следующий пример иллюстрирует практику использования закрытых библиотек и секретных функций для защиты прикладного компонента.
Открытая библиотека РасчетЗарплаты.ibl содержит код подготовки расчета, показа диалога настройки и т.п. Выполнение расчета происходит в закрытой библиотеке ЯдроРасчетаЗарплаты.iсl, которая использует функции общей закрытой библиотеки защиты Защита.iсl.
Исходный текст Защита.ibl (секретный)
#encode = ДА КОНСТ НомерБлока_Зарплата = 10; ФУНКЦИЯ ЛОГИКА: _h_РегистрацияЗарплаты_9459486942362542 РЕЗУЛЬТАТ = КЗ_РЕГИСТРАЦИЯ_БЛОКА(НомерБлока_Зарплата) ЕСЛИ НЕ РЕЗУЛЬТАТ ТО СООБЩЕНИЕ("Лицензии блока ЗАРПЛАТА исчерпаны") КОНЕЦ_ЕСЛИ КОНЕЦ_ФУНКЦИИ ФУНКЦИЯ ЦЕЛОЕ: _h_МаксКолРаботников_56874860673434039 ПЕРЕМ рез = КЗ_СЧИТАТЬ_ЧИСЛО(НомерБлока_Зарплата, "EmplMaxCount", РЕЗУЛЬТАТ) ЕСЛИ рез = -2 ТО РЕЗУЛЬТАТ = 20; // случай демоверсии КОНЕЦ_ЕСЛИ КОНЕЦ_ФУНКЦИИ
Исходный текст ЯдроРасчетаЗарплаты.ibl (секретный)
#encode = ДА ИСПОЛЬЗОВАТЬ("Защита") ТИП ПарамРасчетаЗарплаты [ // ... ] ФУНКЦИЯ ЦЕЛОЕ: КолРаботников(ПарамРасчетаЗарплаты: парам) // ... КОНЕЦ_ЕСЛИ ФУНКЦИЯ ЛОГИКА: РасчетЗарплаты(ПарамРасчетаЗарплаты: парам) ЕСЛИ НЕ _h_РегистрацияЗарплаты_9459486942362542 ТО ВЫХОД(НЕТ) КОНЕЦ_ЕСЛИ ПЕРЕМ Кол = КолРаботников(парам) ПЕРЕМ МаксКол = _h_МаксКолРаботников_56874860673434039 ЕСЛИ Кол > МаксКол ТО СООБЩЕНИЕ("Превышено максимальное количество работников") ВЫХОД(НЕТ) КОНЕЦ_ЕСЛИ // сам расчет... РЕЗУЛЬТАТ = ДА КОНЕЦ_ФУНКЦИИ
Исходный текст РасчетЗарплаты.ibl (публичный)
ИСПОЛЬЗОВАТЬ("ЯдроРасчетаЗарплаты") ФУНКЦИЯ ЛОГИКА: ДлгПарамРасчетаЗарплаты(ПЕРЕМ ПарамРасчетаЗарплаты: парам) // ... КОНЕЦ_ФУНКЦИИ ФУНКЦИЯ ВызовРасчетаЗарплаты ПЕРЕМ ПарамРасчетаЗарплаты: парам ЕСЛИ ДлгПарамРасчетаЗарплаты(парам) ТО ЕСЛИ РасчетЗарплаты(парам) ТО СООБЩЕНИЕ("Расчет зарплаты выполнен успешно.") КОНЕЦ_ЕСЛИ КОНЕЦ_ЕСЛИ КОНЕЦ_ФУНКЦИИ
Директива компилятора #encode = ДА включает создание закрытой библиотеки при добавлении соответствующих исходных текстов в проект.
Исходный текст функции ВызовРасчетаЗарплаты может изменяться дилером или конечным пользователем. Например, вместо показа диалога параметров расчета, может быть выполнено чтение параметров из файла.
Исходный текст функции РасчетЗарплаты доступен только разработчику. У злоумышленника нет возможности вычленить обращение к защите из реализации расчета. Злоумышленник не может создать свой фальшивый файл Защита.icl, содержащий функции-заглушки, т.к. он не знает секретных имен функций, предоставляемых этой библиотекой.