
Часто при передаче продукта заказчику, в виде готовой программы или некоторого аппаратного продукта, необходимо также защитить интеллектуальную собственность в виде исходных текстов программ и скриптов.
Компилируемые языки хоть как-то защищаются, соответственно, с помощью компиляции, хотя и это не панацея. А вот что делать со скриптами, которые могут быть написаны на 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)
MasterMentor
15.07.2025 08:11"обсификациия" скриптов кторые затем дешифруются, чтобы подаваться на интерпретатор - это вообще детский сад. Тема даже не заслуживает обсуждения.
dlinyj
15.07.2025 08:11А есть какие-то решения, сокрытия питонячьих скриптов хорошие?
jnzeax
15.07.2025 08:11Всё зависит от того от кого вы хотите эти скрипты скрыть. Если от любых чужих глаз - вам поможет всё: обфускация, шифрование, автогенерация и.т.д. Но к вам сразу постучат с вопросом не малварь ли вы пишите. А если цель просто интеллектуальную собственность на питоне защитить (если вдруг продаёте софт с исходниками), то уже с лихвой хватит одного литого питон файла, в котором переименованы любые имена в рандомную тарабарщину с сохранением семантики.
dlinyj
15.07.2025 08:11Не, задача банальнее. Делаешь устройство, и там есть одноплатник с прошивкой. Задача скрыть код, чтобы его не могли украсть. Или хотя бы замедлить вскрытие.
jnzeax
15.07.2025 08:11В таком случае всё прозаичнее - если девайс есть физически, его рано или поздно вскроют, вопрос только в том насколько поздно. А способствовать длительности этого процесса может тысяча и одна придуманная техника для анти-дизассемблирование и анти-отладки, всё зависит от множества факторов: целевой архитектуры и её возможностей, скрываетесь вы от софта или от исследователей, ловкости ваших рук и необходимого софта для бинарной инструментации.
dlinyj
15.07.2025 08:11Это общие слова, которые я знаю и без вас. Мне интересно конкретно способы защиты python скриптов.
osj
А потом такой шифрованный скрипт не проходит проверку ДКБ. Для мелких заказчиков может и подойдет такое, для крупняка нет. Сейчас вижу попытки шифровать СПО под видом импортозамещения. И самое смешное, шифруют даже текстовый файл, который нужно подложить по пути, только чтобы не показать, что там нужно заменить руками 2 символа.
Myrmitory
Подтверждаю. Ни одна нормальная контора после проверки такое ПО не примет.
dlinyj
А какие способы обсификации скриптов в больших конторах?