September 10, 2022

Як я захистив свої програми від піратства

В цьому пості я розповім про те як зробити перевірку ліцензій в своїй програмі і не тільки.

Як перевіряються ліцензії?

Значить наша програма відправляє назву програми, телеграм айді користувача, а також ліцензійний ключ на сервер.

На сервері ми маємо зберігати якось данні про користувачів ось які я вибрав колонки:active(чи активна ліцензія),id(унікальний айді в базі данних, призначається автоматично),tg_id(телеграм айді користувача),tg_username(телеграм юзернейм користувача),software_name(назва програми до якої цей ліцензій ключ),key(хеш ліцензійного ключа).
Завдяки хешу якщо хтось отрмає доступ нашої бази данних він не побачить паролю.

Для хешування ми будемо використовувати argon2.
Для роботи з ним є бібліотека argon2-cffi, ставиться вона так:

pip install argon2-cffi

Використовувати її наприклад так:

from argon2 import PasswordHasher
hasher = PasswordHasher()
password_hash = hasher.hash("<якийсь пароль>")
print(password_hash)

Ось документація.

Створення бази данних.

Тепер коли ми знаємо як створювати хеші паролів як створити саму базу данних куди ми їх зможемо записати.
Базу данних ми напишемо на mysql.
Для полегшення життя є прога DBeaver для роботи з базами данних.
Ось сам код для створення бази:

CREATE TABLE `licenses` (
  `active` tinyint(1),
  `id` int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
  `tg_username` varchar(100),
  `tg_id` varchar(30),
  `software_name` varchar(300),
  `key` varchar(100)
);

Тепер пояснення що це все таке.

CREATE TABLE `licenses` (

Вказуємо що наша таблиця буде називатись "licenses".

 `active` tinyint(1),

Поле active, може бути лише 1 або 0.
Tinyint найменший тип чисел в mysql(наскільки я знаю).

  `id` int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,

Унікальне айді кожного елемента в базі.
Int стандартний тип чисел.

`tg_username` varchar(100),

Юзернейм в телеграмі користувача.
Varchar це якийсь недовгий текст.

`tg_id` varchar(30),

Айді користувача в телеграмі.
Айді легше зберігати як символи оскільки так воно не впреться в ліміти чисел(можливо не найкраще рішення).

`software_name` varchar(300),

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

`key` varchar(100)

Ключ, найважливіше поле, конкретніше це хеш пароля(ключа).

);

Кінець команди CREATE(а не сумний смайлик).

Створення сервера для перевірки ліцензій.

Тепер коли у нас є база данних, саме час створити сервер для перевірки ліцензій.
Писати я буду на php бо хостинг який я використовую підтримує лише його.

<?php

Відкриття тегу php.

session_start();
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$link = new mysqli('host', 'username', 'password', 'database');

Запускаємо сесію mysqli(драйвер в php для роботи з mysql).
Створюємо підключення до бази, тут треба замінити host, username, password, database на ваші данні від бази данних.
Зберігаєм підключення до змінни link.

$link->set_charset('utf8mb4');

Ставимо utf8(юнікод).

$key = $_POST["key"];
$soft = $_POST["soft"];
$tg_id = $_POST["tg_id"];

Данні від софта нам кидають через POST запити тому дістаємо ці змінні.

$stmt = $link->prepare("SELECT `key`,active FROM licenses WHERE `tg_id`=? and `software_name`=? LIMIT 1");

Пишемо код для нашого запиту в базу.
Якщо простою мовою тут написано таке щось таке: Вибери один ключ та одну активність_ліцензії з бази данних "ліцензії" де тг_айді=? та назва_софту=?.
Що ж таке ті знаки питання?
Їх ми замінимо на значення телеграм айді та софту але оскільки ми не хочемо щоб нам загнали якусь не хорошу команду(звичайно таблицю не знесуть бо серверу я дав доступ лише до команди SELECT, але можливо зможуть користуватись софтом без ліцензії) то ми будемо вставляти ключ та тг айді через спеціальну команду.

$stmt->bind_param('ss', $tg_id, $soft);

Перший параметр пише які данні ми вставляємо замість знаків питання, в нашому випадку String,String або "ss", наступними параметрами передаємо якраз значення.

$stmt->execute();
$result = $stmt->get_result();
$value = $result->fetch_object();
$isactive=0;

Виконуємо запит до бази і зберігаємо результат в змінну.
Також створюємо змінну в яку запишемо чи активна ліцензія.

if (password_verify($key, $value->key)){
	$isactive=$value->active;
	echo $isactive;
}else{
	echo 0;
}

Перевіряємо чи правильний ключ і записуємо в змінну чи спрацювала ліцензія.

$log  = "Tg id: ".$tg_id.' - '.date("F j, Y, g:i a").PHP_EOL.
	"IP: ".$_SERVER['REMOTE_ADDR'].PHP_EOL.
    "Success: ".$isactive.PHP_EOL.
    "Soft: ".$soft.PHP_EOL.
    "-------------------------".PHP_EOL;
	$filename='./log_'.date("j.n.Y").'.log';
	file_put_contents($filename, $log, FILE_APPEND);
	chmod($filename, 0240);

В змінну лог зберігаєм тг айді, айпі, софт, точний час і чи успішний вхід.
Далі це зберігаємо в файл(його назва буде <сьогоднішня дата>.log) і ставимо на нього так дозволи щоб через ftp ви могли читати але не можна за посиланням було завантажити лог звисайним людям.

<?php
	session_start();
	mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
	$link = new mysqli('host', 'username', 'password', 'database');
	$link->set_charset('utf8mb4'); 
	$key = $_POST["key"];
	$soft = $_POST["soft"];
	$tg_id = $_POST["tg_id"];
	$stmt = $link->prepare("SELECT `key`,active FROM licenses WHERE `tg_id`=? and `software_name`=? LIMIT 1");
	$stmt->bind_param('ss', $tg_id, $soft);
	$stmt->execute();
	$result = $stmt->get_result();
	$value = $result->fetch_object();
	$isactive=0;
	if (password_verify($key, $value->key)){
		$isactive=$value->active;
		echo $isactive;
	}else{
		echo 0;
	}
	$log  = "Tg id: ".$tg_id.' - '.date("F j, Y, g:i a").PHP_EOL.
	"IP: ".$_SERVER['REMOTE_ADDR'].PHP_EOL.
    "Success: ".$isactive.PHP_EOL.
    "Soft: ".$soft.PHP_EOL.
    "-------------------------".PHP_EOL;
	$filename='./log_'.date("j.n.Y").'.log';
	file_put_contents($filename, $log, FILE_APPEND);
	chmod($filename, 0240);
		
?>

Фінальний код.
Якщо всі перевірки пройдені то сервер верне 1.
Отримати логи ви можете за ftp.

Вшивання перевірки ліцензії в програму.

У нас є сервер але на нього мають кидати запити.
Цим і займеться наша програма.
Зараз покажу на прикладі hello world-у.

import requests

Імпортуємо бібліотеку requests через яку і будемо кидати запити.

try:
    with open("license.txt") as l:
        tg_id,key=l.read().strip().split("\n")

Пробуємо відкрити файл і зберегти айді(з першого рядка) а також ключ(з другого рядка) в змінні tg_id та key.

    a=requests.post("https://licenses.rostcraft.pp.ua/licenses.php",data={"tg_id":tg_id,"key":key,"soft":"test_bot"})
except:
    exit()

Кидаємо запит на наш сервер(замініть адресу яку вказав я на власну).
На сервер кидаємо тг айді, ключ і назву софта.
Будь-яка помилка призводить до виходу з програми.
Наскільки я знаю важливо мати https бо це шифрування і tls сертифікат який доводить що це справжній сайт а не його підміна.

if a.status_code!=200:
    exit()
if a.text!="1":
    exit()

Якщо код статусу не 200(200 це успіх), або повернений результат не 1 то ми виходимо з програми.

print("Hello world")

Якщо перевірка пройдена то виходимо з програми.

import requests
try:
    with open("license.txt") as l:
        tg_id,key=l.read().strip().split("\n")
    a=requests.post("https://licenses.rostcraft.pp.ua/licenses.php",data={"tg_id":tg_id,"key":key,"soft":"test_bot"})
except:
    exit()
if a.status_code!=200:
    exit()
if a.text!="1":
    exit()

print("Hello world")

Весь код.

Обфускація коду.

Зараз користувачу нічого не заважає просто видалити перевірку тож ми це виправимо.
Для цього будемо використовувати pyarmor.
Наскільки я знаю він досить безпечний.
Увага наскільки я знаю без використання super mode вашу програму можна вернути в вихідний код.
Нам треба super mode, який на момент написання статті не вийшов для python 3.10 тому я буду використовувати python 3.9, але в можете глянути в документації чи є super mode для вашої версії python.
Тепер зайдіть в папку з вашою прогою і напишіть:

pyarmor obfuscate --advanced=2 .

Це обфускує всі файли .py в папці і кидає їх в папку dist.
--advanced=2 означає super mode.
Також в папці dist буде файл pytransform, в нього основна програма передає обфускований код.
Також ви можете вказати іншу платформу оскільки ті ж файли .pyd лише для вінди.
Ось в документації про те як це зробити: https://pyarmor.readthedocs.io/en/latest/advanced.html#distributing-obfuscated-scripts-to-other-platform.

UPD: цей захист досить легко обійти як я побачив у майбутньому використовуючи підміну запитів, навіть якщо закрити цю дірку, то все одно захист можна буде обійти якщо постаратися.

На цьому все, якщо в захисті будуть знайдені дірки я буду оновлювати статтю.
Всім удачі!
Підготовлено каналом: https://t.me/cryptopidval