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

Компилируемые языки хоть как-то защищаются, соответственно, с помощью компиляции, хотя и это не панацея. А вот что делать со скриптами, которые могут быть написаны на bash или pyton?

Как вариант решения такой проблемы, может быть, шифрование скриптов с аппаратной привязкой дешифратора к платформе, на которой этот скрипт исполняется. Звучит красиво, но надёжно ли? Насколько будет эффективен и взломостойкий этот метод?

У меня в одном проекте была проба пера такого решения. Заодно проверил, и вскрыл это шифрование.

❯ Obash

Поиск готовых решений по шифрации скриптов bash, вывел меня на популярный проект obash, о котором даже есть статья в вики. Само решение прикольно тем, что оно позволяет шифровать также и python-файлы.

Дам вольный перевод вики, что же это такое:

Obash — это обфускатор скриптов bash, написанный на языке программирования C. obash кодирует и шифрует скрипты bash в исполняемые бинарные файлы, аналогично проекту shc, который послужил ему вдохновением, но использует шифрование AES-256, а ключ и инициализационный вектор получают из аппаратного обеспечения вместо того, чтобы быть жестко закодированными в самом бинарном файле. Проект obash был создан для решения некоторых проблем, присущих shc, одной из которых является возможность увидеть исходный скрипт оболочки, просто выполнив команду ps -ef. Хотя цели обоих проектов схожи, obash не разделяет код с shc и был создан с нуля, любые сходства в коде являются случайными и обусловлены общими задачами.Obash всё ещё находится в разработке, однако основная ветка на GitHub обычно содержит рабочие исходники, в то время как тестовая ветка в любой момент может находиться в переходном состоянии.

Также полезная информация, кратко, как же это работает:

Obash берёт входной скрипт и шифрует его с помощью AES-256, а также кодирует полученный шифротекст в base64, чтобы его можно было использовать для объявления массива unsigned char. Затем он создает промежуточный файл на языке C, который в основном является интерпретатором (см. interpreter.c), функциями, массивом текста, содержащим шифротекст, а также необязательными ключом и вектором инициализации для повторно используемых бинарных файлов (не привязанных к аппаратному обеспечению) и основной частью программы. Этот промежуточный файл на C затем компилируется в исполняемый файл.Промежуточный файл на C создаётся следующим образом (см. функцию mk_sh_c в functions.c):
• включает блок из interpreter.h
• содержит переменную crypted_script с зашифрованным с помощью AES-256 скриптом, закодированным в base64
• переменные serial и uuid (пустые, если бинарь не является повторно используемым)
• блок функций из interpreter.h
• основной блок кода main из interpreter.h

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

Проще продемонстрировать, как же это работает, и сразу всё станет понятно.

❯ Пример работы obash

Клонируем репозиторий и ставим нужные либы для работы программы:

git clone https://github.com/louigi600/obash.git
cd obash/
sudo apt install libssl-dev
make

В этом репозитории есть тестовый скрипт, который позволяет сделать проверку шифрования - testme.

#!/bin/bash

echo "my pid is: $$"
echo $0
echo $*
echo -n "enter something ... "
read A
echo $A
sleep 10 &
echo "sleep pid is: $!"
ps -ef | grep $$
ps -ef | grep $! 
sleep 2 

Одной из первых проблем, при попытке зашифровать будет следующий выхлоп:

/obash testme Output filename will be: testme /sys/firmware/dmi/tables/smbios_entry_point: Permission denied /dev/mem: Permission denied Machine uuid: of length -2 /sys/firmware/dmi/tables/smbios_entry_point: Permission denied /dev/mem: Permission denied Insufficient data to identify.

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

Поэтому для упрощения нашего примера, сделаю эти файлики локальными.

sudo cat /sys/devices/virtual/dmi/id/product_uuid > product_uuid 
sudo cat /sys/devices/virtual/dmi/id/product_serial > product_serial

И в коде заменить эти строки в файле interpreter.c

#gv char prod_serial[256]="/sys/devices/virtual/dmi/id/product_serial";
#gv char prod_uuid[256]="/sys/devices/virtual/dmi/id/product_uuid";

на

#gv char prod_serial[256]="./product_serial";
#gv char prod_uuid[256]="./product_uuid";

В файле obfuscated_bash.c

char prod_uuid[256]="/sys/devices/virtual/dmi/id/product_uuid";
char prod_serial[256]="/sys/devices/virtual/dmi/id/product_serial";

на

char prod_uuid[256]="./product_uuid";
char prod_serial[256]="./product_serial";

Теперь всё готово к натурным испытаниям и проверке на прочность.


❯ Анализируем шифрованные скрипты

Итак, создаём шифрованный скрипт:

./obash testme -o testme.x

Можно убедиться, что он работает и запускается. Если же удалить файлы product_serial и product_uuid, то работать перестаёт (потому что файлы содержать ключевые данные для дешифрации).

Давайте проанализируем полученные шифрованные скрипты. Не буду использовать тяжёлую артиллерию типа IDA Pro или ghidra. Для анализа нам потребуется только редактор mcedit или любой аналогичный.

Открываем файл:

mcedit testme.x

И видим следующую картину:

Сразу бросается в глаза блок шифрованных данных BASE64 (1), который сразу раскрывает, что содержимое шифровано. И также целый кусок текста (2), по которому можно загуглить исходники этого проекта. Очень большая ошибка оставлять в теле шифрованной программы любые открытые текстовые данные, которые могут оказать помощь по её вскрытию.

Теперь главное: насколько взломостойкое это решение?

❯ Взлом шифровальщика скриптов

Представим себе, что мы получили доступ к системе, где есть такой шифрованный скрипт. И у нас появилась возможность провести исследование, что же это за программа.

Мы не знаем, что она делает, ни как устроена, и попробуем провести некоторый анализ. Ранее мы её уже посмотрели с помощью mcedit, поняли, что что-то там шифрованное. Больше мы ничего о ней не знаем.

Первое, что сделаем, посмотрим, какие библиотеки она использует:

ldd ./testme.x 
	linux-vdso.so.1 (0x00007ffe010a4000)
	libcrypto.so.3 => /lib/x86_64-linux-gnu/libcrypto.so.3 (0x00007f6bc4372000)
	libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f6bc4149000)
	/lib64/ld-linux-x86-64.so.2 (0x00007f6bc47de000)

Уже понятно, что используется криптографическая библиотека. Давайте запустим программу и посмотрим системные вызовы. Для удобства сохраним в логфайл:

trace -o log ./testme.x

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

...
openat(AT_FDCWD, "./product_uuid", O_RDONLY) = 3
newfstatat(3, "", {st_mode=S_IFREG|0664, st_size=37, ...}, AT_EMPTY_PATH) = 0
read(3, "25219240-d7da-11dd-a7e1-08606e55"..., 4096) = 37
close(3)                                = 0
openat(AT_FDCWD, "./product_serial", O_RDONLY) = 3
newfstatat(3, "", {st_mode=S_IFREG|0664, st_size=21, ...}, AT_EMPTY_PATH) = 0
read(3, "System Serial Number\n", 4096) = 21
close(3)    
...

Главная магия кроется в конце лога:

...
getpid()                                = 105140
mknodat(AT_FDCWD, "/tmp/105140", S_IFIFO|0666) = 0
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f7104fb6a10) = 105141
openat(AT_FDCWD, "/tmp/105140", O_WRONLY) = 3
write(3, "#!/bin/bash\n\necho \"my pid is: $$"..., 175) = 175
close(3)                                = 0
unlink("/tmp/105140")                   = 0
wait4(105141, [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = 105141
...

Опачки, вот оно.

Распишу, что происходит:

Программа получает свой PID процесса равный 105140.
mknodat создаёт именованный канал (FIFO) с именем /tmp/105140.
Далее делается форк программы, в котором, видимо, запускается bash (нам эта часть кода недоступна, потому что это уже другой процесс), PID дочернего процесса равен 105141.
С родительском процессе, который мы исследуем, этот файл открывается для записи и в него записывается 175 байт текстовых данных, которые начинаются с "#!/bin/bash\n\necho \"my pid is: $$". Опа, вот оно!
Далее файл закрывается и удаляется, и после чего родительский процесс ожидает завершение дочернего процесса с PID=105141.

Обратите внимание, что в именованный канал мы записываем 175 байт. И по «странному» стечению обстоятельств, наш тестовый скрипт, до процедуры шифрования, занимает как раз 175 байт!

В принципе, уже с помощью команды strace можно выдрать содержимое скрипта. Но мы пойдём другим путём.

❯ Автоматизируем взлом шифрованных данных

Мы поняли, что это bash скрипт, и таким же образом может быть спрятан и python. Любой шифрованный скрипт должен иметь на выходе нешифрованный интерпретатор, который и является дыркой в безопасности.

Если дверь имеет множество систем защиты, хорошо укреплена, может попробовать проломить стену? Как правило рядом стена из картона и можно легко войти в пролом, не надо биться в укреплённое место.

Поэтому, чтобы не возится с strace, набросаем программу для автоматической дешифрации скриптов. Она будет прикидываться интерпретатором bash и python и позволит сохранить все шифрованные файлы. По-быстрому её, полный код под спойлером.

программа fake_interpreter
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>


#define MAX_BUFFER 65536  // 64 КБ


int main(int argc, char *argv[]) {
    const char *filename = NULL;
    
    char *program_name = strrchr(argv[0], '/');
    program_name = program_name ? program_name + 1 : argv[0];


    if (strcmp(program_name, "bash") == 0) {
        if (argc < 3 || strcmp(argv[1], "-c") != 0) {
            fprintf(stderr, "Использование bash: %s -c \"source <файл>\"\n", argv[0]);
            return 1;
        }
        if (strncmp(argv[2], "source ", 7) == 0) {
            filename = argv[2] + 7;  // Пропускаем "source "
        } else {
            fprintf(stderr, "Ожидалась команда 'source'\n");
            return 1;
        }
    } else if (strcmp(program_name, "python") == 0) {
        if (argc < 3 || strcmp(argv[1], "-u") != 0) {
            fprintf(stderr, "Использование python: %s -u <файл>\n", argv[0]);
            return 1;
        }
        filename = argv[2];
    } else {
        fprintf(stderr, "Неизвестная программа: %s\n", program_name);
        return 1;
    }


    FILE *file = fopen(filename, "r");
    if (file == NULL) {
        fprintf(stderr, "Не удалось открыть файл: %s\n", filename);
        return 1;
    }


    char buffer[MAX_BUFFER];
    size_t bytes_read;
    while ((bytes_read = fread(buffer, 1, sizeof(buffer), file)) > 0) {
        fwrite(buffer, 1, bytes_read, stdout);
    }


    fclose(file);
    return 0;
}

Программу собираем следующим образом:

gcc fake_interpreter.c -o bash
gcc fake_interpreter.c -o python

Далее, простая задача: запустить шифрованную программу с подменой интерпретатора. Делается это простой командой:

PATH=.:$PATH ./testme.x

Всё! Теперь в качестве интерпретатора запускается наш код, и весь шифрованный код вываливается сразу в консоль, после чего его достаточно просто перенаправить в файл.

❯ Выводы

Для меня вывод оказался печальным: это решение нельзя использовать, чтобы скрыть свой код. Оно будет как-то работать при передаче этого файла через открытые сети, но если у взломщика есть физический доступ к машине, то код будет вскрыт.

Ещё одним важным замечанием для всех, кто хочет делать какую-либо минимальную обфускацию кода – это скрывать все текстовые переменные. Так, чтобы невозможно было в программе, открыв mcedit увидеть её содержимое. Это просто важнейшее правило.


Новости, обзоры продуктов и конкурсы от команды Timeweb.Cloud - в нашем Telegram-канале

Перед оплатой в разделе «Бонусы и промокоды» в панели управления активируйте промокод и получите кэшбэк на баланс.

Комментарии (11)


  1. osj
    15.07.2025 08:11

    А потом такой шифрованный скрипт не проходит проверку ДКБ. Для мелких заказчиков может и подойдет такое, для крупняка нет. Сейчас вижу попытки шифровать СПО под видом импортозамещения. И самое смешное, шифруют даже текстовый файл, который нужно подложить по пути, только чтобы не показать, что там нужно заменить руками 2 символа.


    1. Myrmitory
      15.07.2025 08:11

      Подтверждаю. Ни одна нормальная контора после проверки такое ПО не примет.


      1. dlinyj
        15.07.2025 08:11

        А какие способы обсификации скриптов в больших конторах?


  1. MasterMentor
    15.07.2025 08:11

    "обсификациия" скриптов кторые затем дешифруются, чтобы подаваться на интерпретатор - это вообще детский сад. Тема даже не заслуживает обсуждения.


    1. dlinyj
      15.07.2025 08:11

      А есть какие-то решения, сокрытия питонячьих скриптов хорошие?


      1. jnzeax
        15.07.2025 08:11

        Всё зависит от того от кого вы хотите эти скрипты скрыть. Если от любых чужих глаз - вам поможет всё: обфускация, шифрование, автогенерация и.т.д. Но к вам сразу постучат с вопросом не малварь ли вы пишите. А если цель просто интеллектуальную собственность на питоне защитить (если вдруг продаёте софт с исходниками), то уже с лихвой хватит одного литого питон файла, в котором переименованы любые имена в рандомную тарабарщину с сохранением семантики.


        1. dlinyj
          15.07.2025 08:11

          Не, задача банальнее. Делаешь устройство, и там есть одноплатник с прошивкой. Задача скрыть код, чтобы его не могли украсть. Или хотя бы замедлить вскрытие.


          1. jnzeax
            15.07.2025 08:11

            В таком случае всё прозаичнее - если девайс есть физически, его рано или поздно вскроют, вопрос только в том насколько поздно. А способствовать длительности этого процесса может тысяча и одна придуманная техника для анти-дизассемблирование и анти-отладки, всё зависит от множества факторов: целевой архитектуры и её возможностей, скрываетесь вы от софта или от исследователей, ловкости ваших рук и необходимого софта для бинарной инструментации.


            1. dlinyj
              15.07.2025 08:11

              Это общие слова, которые я знаю и без вас. Мне интересно конкретно способы защиты python скриптов.


              1. me21
                15.07.2025 08:11

                Посмотрите в сторону Nuitka. Оно, если я правильно понимаю, транслирует код с Питона на Си, а затем компилирует в исполняемый файл.


                1. dlinyj
                  15.07.2025 08:11

                  Благодарю, ценно