Тема: Возможность обхода проверки сетевых лицензий

Добрый день.
Извините за кликбейтное название поста, но по сути вопрос именно в этом.
Наша компания рассматривает сценарии использования сетевых ключей от Guardant. Загрузил SLK, более менее разобрался, как использовать API для проверки лицензии в C/C++/Python. Также попросили посмотреть на легкость обхода лицензии. Возникла пара вопросов.
1) Тестовая конфигурация:
минимальное тестовое приложение на С, вызывает GrdFeatureLogin()
и GrdFeatureLogout().

const char* visibility =
"{ \"remoteMode\": 2, \
\"controlCenter\": \
{ \
\"hostName\": [ \"127.0.0.1\" ], \
\"connectionTimeout\" : 10 \
} \
}";

И приложение и ControlCenter работают на локальной машине с Ubuntu, на ней же установлена лицензия.

С помощью WireShark записал обмен с ControlCenter на порте 3189. После обмена HTTP  пакетами обмен переходит на WebSockets по grdnet-protocol. Записал payload двух пакетов, отправляемых ControlCenter приложению. Подменил  ControlCenter на сервер, который шлет эти ответы. Результаты для меня неожиданные —  GrdFeatureLogin и Logout успешно отрабатывают для любого feature ID.

Конфигурация не столь искусственна, как может показаться: мы планируем дать пользователю возможность настраивать IP адрес, на котором находится ControlCenter и возможность rehost лицензии.
Собственно, вопрос — это ожидаемое поведение? Если да, то как от него защититься? Усложнить анализ путем увеличения трафика (навтыкать много разных обращений к GCC через API)?

2. Работа через API предполагает использование статической или динамической библиотеки grdlic (как минимум в Linux исполнении).  При линковке нативного приложения я могу использовать статическую библиотеку, убрать символы и обфусцировать. В случае python wrapper предполагается  использовать именно динамическую библиотеку. Так ли я понимаю, что в случае динамической библиотеки нет никакой, предоставляемой guardant, защиты от ее подмены на stub?

Я ни в коей мере не являюсь специалистом по защите или взлому, так что мои вопросы могут показаться тривиальными и ответы очевидными. Тем не менее буду благодарен за ответы)

Re: Возможность обхода проверки сетевых лицензий

Добрый день,

Благодарим за ваше обращение.
Если использовать только Login\Logout в паре с программными ключами, то описанный кейс вполне ожидаемый и если коротко, то решается защитой приложения при помощи протектора Guardant Protection Studio (если тип приложения поддерживаемый нашим протектором) или же внедрением функций шифрования и подписи из нашего Guardant Licensing API.

Немного позже подготовим более раскрытый ответ по методам защиты от этого через API.

Re: Возможность обхода проверки сетевых лицензий

Александра, спасибо за ответ.
Действительно, Protection Studio не дает залогиниться в компонент с помощью простого replay пакетов.
Но мы предпочли бы использовать API.

Немного позже подготовим более раскрытый ответ по методам защиты от этого через API.

Буду признателен за обзор методов защиты через API.

Re: Возможность обхода проверки сетевых лицензий

Павел, здравствуйте!

Приносим свои извинения за длительную паузу с ответом.

ВОПРОС 1

Павел пишет:

навтыкать много разных обращений к GCC через API)

Фактически — верно, но нужно определить какие функции API нужно вызывать и почему.
В действительности фундаментом для защиты являются функции шифрования и подписи данных.

Почему:

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

  • Потому что криптографические вычисления выполняются именно ключами Guardant в изолированной среде. Проще говоря — данные попадают в сам ключ, где уже шифруются и\или подписываются. То есть при правильном подходе защищенное приложение в подавляющем числе случаев будет понимать: где данные от легального ключа с лицензией, а где попытка подмены и взлома.
    Комплекс подобных мероприятий со сложной логикой реализован как раз в протекторе Guardant Protection Studio, а в дополнение к этому — сложные технологии обфускации и виртуализации кода, защищаемого приложения. Из недостатков — у протекторов кода, не только нашего, а в принципе, есть свойство замедления кода, который был выбран для защиты, а также меньшая универсальность, с точки зрения покрытия приложений, написанных на различных языках программирования.

Что делать при помощи API?

  • Расставить в приложении вызовы GrdFeatureCheck с заданными параметрами publicKeySize и publicKey. Функция будет для случайных данных вычислять цифровую подпись (точнее передавать эти данные в ключ и ожидать буфер с подсчитанной подписью) и проверять эту же подпись (уже в защищенном приложении). Каждый вызов — проверка подписи для новых данных. Запись payload теряет смысл.

  • Для усиления, использовать функции GrdFeatureEncrypt \ GrdFeatureDecrypt . Шифрование и расшифровка буфера с данными, которые будут поданы на вход этим функциям. Тут данные кодируются и декодируются всегда в ключе Guardant, а функции передают данные и дополнительные параметры (режим шифрования и контекст). Тут надо иметь в виду, что если, от вызова к вызову, в ключ отправлять всегда неизменный буфер с данными, с одними и теми же параметрами, то и результат будет всегда один, т. к. используется симметричный алгоритм шифрования. Но если заморочиться, то это мощный инструмент.

  • И дополнить все можно парой функций GrdFeatureSign \ GrdVerifyDigest . Здесь тоже вычисляется и проверяется цифровая подпись, но данные, для которых это делается, выбирает уже сам разработчик защищенного приложения.

Дополню картину еще некоторыми цитатами из документации:

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

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

  3. Используйте разные вопросы в разных версиях приложения. Если в разных приложениях или разных версиях одного и того же продукта будут использоваться разные вопросы к алгоритму (т. е. разные блоки кодированных данных), это даст гарантию того, что хакер не сможет создать универсальный инструмент для взлома всех продуктов (или всех их версий). Даже если хакер исхитрится-таки создать эмулятор для приложения, выход его новой версии заставит хакера производить свою работу заново – ведь новая версия приложения работает уже с другими кодированными данными.

  4. Воспользуйтесь алгоритмом цифровой подписи. Он позволяет подписывать произвольные данные. Лучше всего, чтобы это были данные, которые возникают в приложении естественным образом (так называемая естественная энтропия): данные, которые пользователь вводит в программу с клавиатуры, движения мышки, значения из баз данных, полученные по запросу пользователя. Тогда простая табличная эмуляция станет невозможной. Проверяйте правильность подписи. Однако не следует использовать простое сравнения возвращаемого ею значения, т. к. сравнение по принципу «да/нет» легко сломать с помощью т. н. битхака (исправляется всего 1 бит в программе). Лучше всего собирать возвращаемые значения (например, из битов возврата GrdVerifyDigest делать 32-битные числа), проверять подпись случайных данных (создавать ложные цели для анализа хакером) и на основе большого числа вызовов GrdFeatureSign / GrdVerifyDigest создавать значения, которые в дальнейшем можно использовать в работе защищенного приложения.

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

ВОПРОС 2

Павел пишет:

В случае python wrapper предполагается  использовать именно динамическую библиотеку. Так ли я понимаю, что в случае динамической библиотеки нет никакой, предоставляемой guardant, защиты от ее подмены на stub?

Конкретно для Python можно рассмотреть описанный тут вариант "Python. Как внедрить автоматическую защиту"

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