You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 13 Current »

Кроме защиты native (написанные, например, на C) и .Net файлов приложений Guardant Protection Studio может решить задачу и по защите Python-приложений.

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

При защите Python-приложений есть несколько подходов:

1. Вынести небольшую, но критичную для работы всего сервиса, часть кода во внешнюю native-библиотеку, которую защитить с помощью Guardant Licensing API и Guardant Protection Studio.
Такой подход обеспечивает высокий уровень безопасности и гибкости, но при этом требует от разработчика умения программировать на компилируемом языке и потратить время на портирование части кода.

2. Использовать решение Cython, которое позволяет осуществить трансляцию Python кода в C код. Такой подход позволяет без переписывания кода вынести часть логики в native-файл, который будет надежно защищен посредством Guardant Licensing API и/или Guardant Protection Studio.
Ниже в документации представлена подробная инструкция по реализации такого подхода к защите Python-приложений с использование Cython и Guardant SLK.

Понадобятся файлы

setup.py

скрипт настройки окружения cython и автоматической компиляции кода 

run_python.py

пример исходного кода на Python, в котором проводятся вычисления и вызываются функции Guardant 

run_cython.pyx

пример исходного кода с измененными прототипами функций для Cython. По сути тот же код на Python, но с явно указанными типами

simple_test.py

пример простого приложения, которое вызывает защищенные функции/функции API

Защита Python-приложения с использованием Cython в Debian 11

Шаг 1. Установить cython и нужные ему пакеты

sudo apt-get install -y build-essential python3-dev
sudo apt install -y python3-pip
sudo apt install -y cython3
pip3 install setuptools
pip3 install requests

Если при использовании pip возникает ошибка вида "error: externally-managed-environment" дополнительно выполнить команды для создания окружения.

sudo apt install -y python3-venv 
python3 -m venv myenv
source myenv/bin/activate

И затем повторить.

pip3 install setuptools
pip3 install requests

Шаг 2. Установить Guardant SLK из tar.gz архива

tar -xf ./GuardantSLK-.tar.gz

Шаг 3. Создать рабочую папку 

mkdir mydirname

Шаг 4. Поместить в рабочую папку файлы

setup.py
run_cython.pyx  
run_python.py  
simple_test.py

grdlic_python.py          <--- лежит по пути .../Software Licensing Kit/api/wrappers/python
libgrdlic.a                      <--- лежит по пути .../Software Licensing Kit/api/lib/linux/x86_64
libgrdlic.so                    <--- лежит по пути .../Software Licensing Kit/api/lib/linux/x86_64

Шаг 5. Активировать DL или подключить аппаратный ключ Sign

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

Шаг 6. Сгенерировать код на С и скомпилировать расширение

Для генерации нужно выполнить команду:

python3 setup.py build_ext --inplace

В одной директории с исходными файлами, будут созданы новые:
run_cython.c
run_cython.cpython-39-x86_64-linux-gnu.so

Можно использовать run_cython.cpython-39-x86_64-linux-gnu.so для выбора функций для защиты, пропустить шаги 7-9, а на шаге 10 не указывать .map файл.
Однако для больших проектов рекомендуется .map файл указывать. Для его получения в нужном формате надо выполнить шаги 7-9.

Шаг 7.

При компиляции таким способом не создается .map файл. Чтобы его получить, необходимо установить английский язык для генерации map-файла (это требуется для корректного парсинга протектором)

export LANG=en_US.UTF-8

Шаг 8. Посмотреть версию python3, используемую в системе

python3 --version

В консоли будет указана версия, которую надо использовать в следующем шаге, например: Python 3.11.2 .

Шаг 9. Скомпилировать код из run_cython.c и создать .map файл

gcc -shared -pthread -fPIC -fwrapv -O2 -Wall -fno-strict-aliasing \
     -I/usr/include/python3.11 -o run_cython.so run_cython.c -Xlinker -Map=output.map 

Компилятор создаст файлы: 
run_cython.so
output.map 

Шаг 10. Подготовить проект защиты

Открыть Protection Studio, авторизоваться, выбрать файл "run_cython.so" из шага выше для защиты и указать компонент (любой из имеющихся в ключе).

При наличии выбрать .map файл, созданный на шаге 8.

В окне выбора функций для защиты, ввести в строку поиска "factorial" и выбрать "__pyx_pw_10run_cython_1factorial"                 

Шаг 11. Запустить защиту

Дождаться завершения процесса защиты.

Шаг 12. Скопировать защищенный run_cython.so и libprotectstorage64.so в рабочую папку

Шаг 13. Очистить от лишних файлов директорию с защищенными

Удалить, переименовать или переместить в другую папку файлы (чтобы убедиться в работе защищенного run_cython.so):
run_cython.c
run_cython.pyx
*run_cython.cpython-39-x86_64-linux-gnu.so (если run_cython.so был скомпилирован вручную для получения .map файла)

Шаг 14. Запустить simple_test.py

python3 simple_test.py

Запустится простой тест, который посчитает факториал от пяти и выполнит вызовы функций API.

Подробнее процесс работы с Cython описан в его документации:
https://cython.readthedocs.io/en/latest/src/userguide/source_files_and_compilation.html

Cython имеет ряд особенностей

1) Cython создает большое количество вспомогательных внутренних функций. При защите важно выбрать именно пользовательскую функцию, а не внутреннюю. Если по ошибке выбрана внутренняя служебная функция для защиты в Protection Studio, высока вероятность, что защита не будет работать корректно.

2) Cython при генерации кода на С добавляет префикс к названию оригинальной функции на Python, изменяя его.

Например, есть python-функция подсчета факторила и печати результата:

def factorial(x):    
    y = 1    
    for i in range(1, x+1):        
        y *= i        								

    print("Factorial(" + str(x) + ")= " + str(y))

    return y

В коде на С, сгенерированном через cython, она выглядит так:

static PyObject *__pyx_pw_10run_cython_1factorial(PyObject *__pyx_self, PyObject *__pyx_arg_x); /*proto*/
static int __pyx_f_10run_cython_factorial(int __pyx_v_x, CYTHON_UNUSED int __pyx_skip_dispatch) {
  int __pyx_v_y;
  int __pyx_v_i;
  int __pyx_r;
  __Pyx_RefNannyDeclarations
  long __pyx_t_1;
  long __pyx_t_2;
  int __pyx_t_3;
  PyObject *__pyx_t_4 = NULL;
  PyObject *__pyx_t_5 = NULL;
  PyObject *__pyx_t_6 = NULL;
  int __pyx_lineno = 0;
  const char *__pyx_filename = NULL;
  int __pyx_clineno = 0;
  __Pyx_RefNannySetupContext("factorial", 0);  
/* "run_cython.pyx":15
 * 
 * cpdef int factorial(int x):
 *     cdef int y = 1             # <<<<<<<<<<<<<<
 *     cdef int i
 * 
 */
  __pyx_v_y = 1;  
/* "run_cython.pyx":18
 *     cdef int i
 * 
 *     for i in range(1, x+1):             # <<<<<<<<<<<<<<
 *         y *= i
 * 
 */
  __pyx_t_1 = (__pyx_v_x + 1);
  __pyx_t_2 = __pyx_t_1;
  for (__pyx_t_3 = 1; __pyx_t_3 < __pyx_t_2; __pyx_t_3+=1) {
    __pyx_v_i = __pyx_t_3;    
/* "run_cython.pyx":19
 * 
 *     for i in range(1, x+1):
 *         y *= i             # <<<<<<<<<<<<<<
 * 
 *     print("Factorial(" + str(x) + ")= " + str(y))
 */
    __pyx_v_y = (__pyx_v_y * __pyx_v_i);
  }  
/* "run_cython.pyx":21
 *         y *= i
 * 
 *     print("Factorial(" + str(x) + ")= " + str(y))             # <<<<<<<<<<<<<<
 * 
 *     return y
 */
  __pyx_t_4 = __Pyx_PyInt_From_int(__pyx_v_x); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 21, __pyx_L1_error)
  __Pyx_GOTREF(__pyx_t_4);
  __pyx_t_5 = __Pyx_PyObject_CallOneArg(((PyObject *)(&PyString_Type)), __pyx_t_4); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 21, __pyx_L1_error)
  __Pyx_GOTREF(__pyx_t_5);
  __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0;
  __pyx_t_4 = PyNumber_Add(__pyx_kp_s_Factorial, __pyx_t_5); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 21, __pyx_L1_error)
  __Pyx_GOTREF(__pyx_t_4);
  __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
  __pyx_t_5 = PyNumber_Add(__pyx_t_4, __pyx_kp_s_); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 21, __pyx_L1_error)
  __Pyx_GOTREF(__pyx_t_5);
  __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0;
  __pyx_t_4 = __Pyx_PyInt_From_int(__pyx_v_y); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 21, __pyx_L1_error)
  __Pyx_GOTREF(__pyx_t_4);
  __pyx_t_6 = __Pyx_PyObject_CallOneArg(((PyObject *)(&PyString_Type)), __pyx_t_4); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 21, __pyx_L1_error)
  __Pyx_GOTREF(__pyx_t_6);
  __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0;
  __pyx_t_4 = PyNumber_Add(__pyx_t_5, __pyx_t_6); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 21, __pyx_L1_error)
  __Pyx_GOTREF(__pyx_t_4);
  __Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
  __Pyx_DECREF(__pyx_t_6); __pyx_t_6 = 0;
  if (__Pyx_PrintOne(0, __pyx_t_4) < 0) __PYX_ERR(0, 21, __pyx_L1_error)
  __Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0;  
/* "run_cython.pyx":23
 *     print("Factorial(" + str(x) + ")= " + str(y))
 * 
 *     return y             # <<<<<<<<<<<<<<
 * 
 * cpdef str uhex(value):
 */
  __pyx_r = __pyx_v_y;
  goto __pyx_L0;

Т.е. исходная функция называется "factorial", а сгенерированная - "__pyx_pw_10run_cython_1factorial"

Как следствие, перед защитой нужно обязательно изучить сгенерированный код на С, чтобы выбрать нужную функцию для защиты. Как видно в примере выше, сгенерированный код имеет подробные комментарии: явно указано, какая строка исходного кода на Python преобразована в какой код на С.

3) На просторах интернета можно найти примеры, где Cython генерирует неправильный или не совсем правильный код С (в сложных проектах). Таким образом его использовать быстро и достаточно просто, но опытный разработчик сделает код на C лучше.

  • No labels