Заметка Вирус в Shell-скрипте: как встроить и защитить полезную нагрузку

1785351257156.png


Shell-скрипт может содержать не только команды Bash, но и дополнительные данные:

  • конфигурационные файлы;
  • шаблоны;
  • сертификаты и публичные ключи;
  • архив с программой;
  • статические ресурсы;
  • вспомогательные исполняемые файлы;
  • другой Shell-скрипт;
  • зашифрованный контейнер.

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

В статье рассмотрим способы упаковки и защиты данных внутри .sh-файла.

---

## Содержание

1. Кодирование, упаковка и шифрование
2. Встраивание текста через Here Document
3. Встраивание бинарного файла через Base64
4. Создание самораспаковывающегося Shell-скрипта
5. Автоматическая сборка установщика
6. Использование Makeself
7. Защита Shell-скрипта с помощью SHC
8. Шифрование через age
9. Шифрование через OpenSSL
10. Проверка целостности и подлинности
11. Безопасная работа с временными файлами
12. Как исследовать неизвестный самораспаковывающийся скрипт
13. Что выбрать на практике

---

# 1. Кодирование, упаковка и шифрование

Сначала нужно различать несколько понятий.

## Кодирование

Кодирование изменяет представление данных, но не защищает их.

Пример:

Bash:
base64 program.bin > program.bin.b64

Восстановление:

Bash:
base64 -d program.bin.b64 > program.bin

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

## Сжатие

Сжатие уменьшает размер данных:

Bash:
tar -czf payload.tar.gz payload/

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

## Обфускация

Обфускация делает код менее понятным:

Bash:
a="ec"
b="ho"
"$a$b" "Hello"

Результат:

Код:
Hello

Обфускация может затруднить чтение исходника, но не обеспечивает настоящей секретности.

## Шифрование

Шифрование защищает данные криптографически. Для расшифровки потребуется пароль или закрытый ключ.

Подходящие инструменты:

  • age;
  • GnuPG;
  • OpenSSL;
  • системы управления секретами;
  • аппаратные криптографические токены.

Для новых проектов удобнее всего использовать age. Это специализированная утилита шифрования файлов, поддерживающая пароли, собственные ключи и SSH-ключи.

---

# 2. Встраивание текста через Here Document

Самый простой вариант — разместить текстовый файл непосредственно внутри скрипта.

Bash:
#!/usr/bin/env bash

set -Eeuo pipefail

cat > application.conf <<'CONFIG'
server {
    listen = 127.0.0.1
    port = 8080
    debug = false
}
CONFIG

chmod 600 application.conf

echo "Конфигурация создана: application.conf"

Конструкция:

Bash:
<<'CONFIG'

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

Например:

Bash:
cat <<'TEXT'
Пользователь: $USER
Каталог: $HOME
TEXT

выведет буквальный текст:

Код:
Пользователь: $USER
Каталог: $HOME

Если убрать кавычки:

Bash:
cat <<TEXT
Пользователь: $USER
Каталог: $HOME
TEXT

Bash подставит реальные значения переменных.

Here Document хорошо подходит для:

  • конфигурационных файлов;
  • systemd-юнитов;
  • небольших HTML-страниц;
  • шаблонов Nginx;
  • SQL-запросов;
  • публичных ключей;
  • коротких вспомогательных скриптов.

---

# 3. Встраивание бинарного файла через Base64

Бинарный файл нельзя безопасно вставить в обычный текстовый блок. Сначала его можно преобразовать в Base64.

Допустим, есть файл:

Код:
helper.bin

Кодируем его:

Bash:
base64 helper.bin > helper.bin.b64

После этого содержимое helper.bin.b64 можно разместить внутри Shell-скрипта:

Bash:
#!/usr/bin/env bash

set -Eeuo pipefail
umask 077

OUTPUT="./helper.bin"

base64 -d > "$OUTPUT" <<'PAYLOAD'
f0VMRgIBAQAAAAAAAAAAAAIAPgABAAAA...
PAYLOAD

chmod 700 "$OUTPUT"

echo "Файл восстановлен: $OUTPUT"

Здесь строка:

Bash:
base64 -d

декодирует встроенные данные и записывает результат в helper.bin.

Важно понимать: Base64 ничего не шифрует. Содержимое можно извлечь обычной командой.

---

# 4. Самораспаковывающийся Shell-скрипт

Более практичный вариант — разместить в начале файла исполняемый Shell-код, а после специального маркера добавить архив.

Структура файла будет выглядеть так:

Код:
#!/usr/bin/env bash

Shell-код установщика...

__ARCHIVE_BELOW__
H4sIAAAAAAAAA...

Скрипт:

1. определяет номер строки с маркером;
2. читает всё, что находится после него;
3. декодирует Base64;
4. проверяет контрольную сумму;
5. распаковывает архив.

## Пример распаковщика

Bash:
#!/usr/bin/env bash

set -Eeuo pipefail
umask 077

readonly EXPECTED_SHA256="ВСТАВИТЬ_SHA256_АРХИВА"

SELF_PATH="$(readlink -f "$0" 2>/dev/null || printf '%s' "$0")"
DESTINATION="${1:-./extracted-payload}"

TEMP_DIR="$(mktemp -d)"

cleanup() {
    rm -rf -- "$TEMP_DIR"
}

trap cleanup EXIT INT TERM

ARCHIVE_LINE="$(
    awk '/^__ARCHIVE_BELOW__$/ {
        print NR + 1
        exit
    }' "$SELF_PATH"
)"

if [[ -z "$ARCHIVE_LINE" ]]; then
    echo "Ошибка: встроенный архив не найден." >&2
    exit 1
fi

ARCHIVE_PATH="$TEMP_DIR/payload.tar.gz"

tail -n +"$ARCHIVE_LINE" "$SELF_PATH" |
    base64 -d > "$ARCHIVE_PATH"

ACTUAL_SHA256="$(
    sha256sum "$ARCHIVE_PATH" |
        awk '{print $1}'
)"

if [[ "$ACTUAL_SHA256" != "$EXPECTED_SHA256" ]]; then
    echo "Ошибка: контрольная сумма архива не совпадает." >&2
    echo "Ожидалось: $EXPECTED_SHA256" >&2
    echo "Получено:  $ACTUAL_SHA256" >&2
    exit 1
fi

mkdir -p -- "$DESTINATION"

tar -xzf "$ARCHIVE_PATH" \
    -C "$DESTINATION" \
    --no-same-owner \
    --no-same-permissions

echo "Полезная нагрузка распакована в: $DESTINATION"

exit 0

__ARCHIVE_BELOW__

После маркера нужно добавить архив, преобразованный в Base64:

Bash:
base64 payload.tar.gz >> installer.sh

Затем:

Bash:
chmod 700 installer.sh

Запуск:

Bash:
./installer.sh

Или с указанием каталога:

Bash:
./installer.sh /opt/my-application

---

# 5. Автоматическая сборка установщика

Вручную подставлять контрольную сумму и добавлять архив неудобно. Лучше создать отдельный сборочный скрипт.

## Структура проекта

Код:
project/
├── build.sh
├── payload/
│   ├── install.sh
│   ├── application.conf
│   └── bin/
│       └── application
└── output/

## Скрипт build.sh

Bash:
#!/usr/bin/env bash

set -Eeuo pipefail
umask 077

readonly PROJECT_DIR="$(
    cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &&
        pwd
)"

readonly PAYLOAD_DIR="$PROJECT_DIR/payload"
readonly OUTPUT_DIR="$PROJECT_DIR/output"
readonly OUTPUT_FILE="$OUTPUT_DIR/installer.sh"

TEMP_DIR="$(mktemp -d)"

cleanup() {
    rm -rf -- "$TEMP_DIR"
}

trap cleanup EXIT INT TERM

if [[ ! -d "$PAYLOAD_DIR" ]]; then
    echo "Ошибка: каталог payload не найден." >&2
    exit 1
fi

mkdir -p -- "$OUTPUT_DIR"

ARCHIVE="$TEMP_DIR/payload.tar.gz"

tar -C "$PAYLOAD_DIR" -czf "$ARCHIVE" .

ARCHIVE_SHA256="$(
    sha256sum "$ARCHIVE" |
        awk '{print $1}'
)"

cat > "$OUTPUT_FILE" <<EOF
#!/usr/bin/env bash

set -Eeuo pipefail
umask 077

readonly EXPECTED_SHA256="$ARCHIVE_SHA256"

SELF_PATH="\$(readlink -f "\$0" 2>/dev/null || printf '%s' "\$0")"
DESTINATION="\${1:-./extracted-payload}"

TEMP_DIR="\$(mktemp -d)"

cleanup() {
    rm -rf -- "\$TEMP_DIR"
}

trap cleanup EXIT INT TERM

ARCHIVE_LINE="\$(
    awk '/^__ARCHIVE_BELOW__\$/ {
        print NR + 1
        exit
    }' "\$SELF_PATH"
)"

if [[ -z "\$ARCHIVE_LINE" ]]; then
    echo "Ошибка: встроенный архив не найден." >&2
    exit 1
fi

ARCHIVE_PATH="\$TEMP_DIR/payload.tar.gz"

tail -n +"\$ARCHIVE_LINE" "\$SELF_PATH" |
    base64 -d > "\$ARCHIVE_PATH"

ACTUAL_SHA256="\$(
    sha256sum "\$ARCHIVE_PATH" |
        awk '{print \$1}'
)"

if [[ "\$ACTUAL_SHA256" != "\$EXPECTED_SHA256" ]]; then
    echo "Ошибка проверки контрольной суммы." >&2
    exit 1
fi

mkdir -p -- "\$DESTINATION"

tar -xzf "\$ARCHIVE_PATH" \
    -C "\$DESTINATION" \
    --no-same-owner \
    --no-same-permissions

echo "Файлы распакованы в: \$DESTINATION"

exit 0

__ARCHIVE_BELOW__
EOF

base64 "$ARCHIVE" >> "$OUTPUT_FILE"

chmod 700 "$OUTPUT_FILE"

echo "Установщик создан: $OUTPUT_FILE"
echo "SHA-256 архива:    $ARCHIVE_SHA256"

Делаем скрипт исполняемым:

Bash:
chmod 700 build.sh

Собираем установщик:

Bash:
./build.sh

Результат:

Код:
output/installer.sh

## Почему установщик только распаковывает файлы

Безопаснее не запускать встроенный код автоматически.

Пользователь сможет сначала проверить содержимое:

Bash:
./installer.sh ./inspection

Затем:

Bash:
find ./inspection -type f -maxdepth 3 -print

После проверки установочный файл можно запустить вручную:

Bash:
sudo ./inspection/install.sh

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

---

# 6. Использование Makeself

Необязательно создавать собственный формат установщика. Для этого существует утилита Makeself.

Код:
sudo apt update
sudo apt install makeself

Makeself создаёт самораспаковывающийся архив, который выглядит как Shell-скрипт и обычно имеет расширение .run. В начало файла добавляется Shell-загрузчик, а после него располагается сжатый TAR-архив. Makeself также поддерживает встроенную проверку целостности.

## Подготовка каталога

Код:
package/
├── install.sh
├── application.conf
└── application.bin

Файл package/install.sh:

Bash:
#!/usr/bin/env bash

set -Eeuo pipefail

echo "Каталог распаковки: $(pwd)"
echo "Файлы установщика подготовлены."

# Здесь должна находиться прозрачная логика установки.
# Не рекомендуется запускать загруженный из сети код.

Разрешаем выполнение:

Bash:
chmod 700 package/install.sh

## Создание архива

Bash:
makeself \
    --gzip \
    ./package \
    ./application-installer.run \
    "Application Installer" \
    ./install.sh

Запуск:

Bash:
chmod 700 application-installer.run
./application-installer.run

## Распаковка без запуска установщика

Перед выполнением архив можно распаковать для проверки:

Bash:
./application-installer.run \
    --noexec \
    --target ./inspection

После этого следует изучить содержимое:

Bash:
find ./inspection -type f -print

И проверить Shell-код:

Bash:
bash -n ./inspection/install.sh

## Когда использовать Makeself

Makeself подходит для:

  • автономных установщиков;
  • доставки программы без пакетного менеджера;
  • офлайн-установки;
  • внутренних корпоративных инструментов;
  • аварийных комплектов восстановления;
  • программ, состоящих из нескольких файлов.

---

# 7. Защита Shell-скрипта с помощью SHC

Утилита SHC, или Shell Script Compiler, преобразует Shell-скрипт в исходный код на C, а затем компилирует его в исполняемый файл.

При этом SHC не превращает сценарий в полностью независимую нативную программу. Полученный файл по-прежнему зависит от интерпретатора, указанного в строке shebang, например /bin/bash или /bin/sh.

## Установка зависимостей

Bash:
sudo apt update
sudo apt install build-essential
Код:
sudo apt update
sudo apt install shc

После установки SHC компиляция выполняется так:

Bash:
shc -f application.sh -o application

Получатся файлы:

Код:
application
application.sh.x.c

Файл:

Код:
application

можно запускать как обычную программу:

Bash:
./application

Сгенерированный C-файл можно удалить:

Bash:
rm -f application.sh.x.c

## Что даёт SHC

SHC позволяет:

  • скрыть исходный текст от беглого просмотра;
  • распространять Shell-сценарий как исполняемый файл;
  • уменьшить вероятность случайного изменения скрипта;
  • усложнить копирование отдельных функций.

## Чего SHC не даёт

SHC не является надёжным хранилищем секретов.

Не следует помещать внутрь такого файла:

  • постоянные API-ключи;
  • пароли баз данных;
  • закрытые SSH-ключи;
  • мастер-пароли;
  • токены облачных провайдеров;
  • ключи расшифровки собственной полезной нагрузки.

Во время выполнения команды и данные должны попасть в память процесса. Поэтому специалист с доступом к системе потенциально сможет провести анализ исполняемого файла.

Правильнее считать SHC средством обфускации и упаковки, а не криптографической защиты.

---

# 8. Шифрование через age

age — удобный современный инструмент для шифрования файлов. В Debian 12 и Ubuntu 22.04 и новее он доступен через пакетный менеджер.

Установка:

Bash:
sudo apt update
sudo apt install age

## Шифрование паролем

Сначала создаём архив:

Bash:
tar -czf payload.tar.gz payload/

Шифруем:

Bash:
age \
    --passphrase \
    --output payload.tar.gz.age \
    payload.tar.gz

Утилита запросит пароль.

Расшифровка:

Bash:
age \
    --decrypt \
    --output payload.tar.gz \
    payload.tar.gz.age

## Текстовый формат

Для размещения зашифрованного файла внутри Shell-скрипта удобно использовать ASCII Armor:

Bash:
age \
    --passphrase \
    --armor \
    --output payload.age.txt \
    payload.tar.gz

Получится текст примерно такого вида:

Код:
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3Yx...
-----END AGE ENCRYPTED FILE-----

Его можно добавить после маркера:

Код:
__ENCRYPTED_PAYLOAD_BELOW__
-----BEGIN AGE ENCRYPTED FILE-----
...
-----END AGE ENCRYPTED FILE-----

## Расшифровка встроенного контейнера

Bash:
#!/usr/bin/env bash

set -Eeuo pipefail
umask 077

SELF_PATH="$(readlink -f "$0" 2>/dev/null || printf '%s' "$0")"
DESTINATION="${1:-./decrypted-payload}"

TEMP_DIR="$(mktemp -d)"

cleanup() {
    rm -rf -- "$TEMP_DIR"
}

trap cleanup EXIT INT TERM

PAYLOAD_LINE="$(
    awk '/^__ENCRYPTED_PAYLOAD_BELOW__$/ {
        print NR + 1
        exit
    }' "$SELF_PATH"
)"

if [[ -z "$PAYLOAD_LINE" ]]; then
    echo "Ошибка: зашифрованная нагрузка не найдена." >&2
    exit 1
fi

ENCRYPTED_FILE="$TEMP_DIR/payload.age"
ARCHIVE_FILE="$TEMP_DIR/payload.tar.gz"

tail -n +"$PAYLOAD_LINE" "$SELF_PATH" > "$ENCRYPTED_FILE"

age \
    --decrypt \
    --output "$ARCHIVE_FILE" \
    "$ENCRYPTED_FILE"

mkdir -p -- "$DESTINATION"

tar -xzf "$ARCHIVE_FILE" \
    -C "$DESTINATION" \
    --no-same-owner \
    --no-same-permissions

echo "Данные расшифрованы в: $DESTINATION"

exit 0

__ENCRYPTED_PAYLOAD_BELOW__

После маркера добавляется содержимое файла:

Bash:
cat payload.age.txt >> encrypted-installer.sh
chmod 700 encrypted-installer.sh

При запуске:

Bash:
./encrypted-installer.sh

age запросит пароль.

## Шифрование публичным ключом

Пароль можно не использовать. Вместо этого создаётся пара ключей.

Генерация закрытого ключа:

Bash:
age-keygen -o identity.txt

Получение публичного ключа:

Bash:
age-keygen -y identity.txt > recipient.txt

Шифрование:

Bash:
age \
    --recipients-file recipient.txt \
    --output payload.tar.gz.age \
    payload.tar.gz

Расшифровка:

Bash:
age \
    --decrypt \
    --identity identity.txt \
    --output payload.tar.gz \
    payload.tar.gz.age

Файл recipient.txt можно передавать свободно. Файл identity.txt необходимо защищать:

Bash:
chmod 600 identity.txt

## Не встраивайте пароль в скрипт

Плохой вариант:

Bash:
PASSWORD="my-super-password"

age --decrypt \
    --passphrase \
    payload.age

И ещё хуже:

Bash:
echo "$PASSWORD" | some-decryption-command

Пароль может попасть:

  • в исходный файл;
  • в резервную копию;
  • в историю Git;
  • в журналы;
  • в дамп памяти;
  • в переменные окружения процесса;
  • в систему мониторинга.

Лучше использовать:

  • интерактивный ввод;
  • закрытый ключ;
  • системное хранилище секретов;
  • HashiCorp Vault;
  • systemd credentials;
  • Docker secrets;
  • Kubernetes Secrets с дополнительным шифрованием;
  • аппаратный токен.

---

# 9. Шифрование через OpenSSL

OpenSSL также позволяет зашифровать файл с помощью пароля.

## Шифрование

Bash:
openssl enc \
    -aes-256-cbc \
    -salt \
    -pbkdf2 \
    -iter 200000 \
    -in payload.tar.gz \
    -out payload.tar.gz.enc

## Расшифровка

Bash:
openssl enc \
    -d \
    -aes-256-cbc \
    -salt \
    -pbkdf2 \
    -iter 200000 \
    -in payload.tar.gz.enc \
    -out payload.tar.gz

Опция:

Код:
-pbkdf2

включает получение ключа из пароля через PBKDF2.

Опция:

Код:
-iter 200000

задаёт число итераций функции получения ключа.

Опция:

Код:
-salt

включает использование случайной соли. В OpenSSL соль используется по умолчанию, а документация отдельно предупреждает, что -nosalt не следует применять, кроме специальных случаев совместимости и тестирования.

## Текстовый вывод

Зашифрованный файл можно сразу преобразовать в Base64:

Bash:
openssl enc \
    -aes-256-cbc \
    -salt \
    -pbkdf2 \
    -iter 200000 \
    -a \
    -in payload.tar.gz \
    -out payload.tar.gz.enc.txt

Расшифровка:

Bash:
openssl enc \
    -d \
    -aes-256-cbc \
    -salt \
    -pbkdf2 \
    -iter 200000 \
    -a \
    -in payload.tar.gz.enc.txt \
    -out payload.tar.gz

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

---

# 10. Проверка целостности и подлинности

Шифрование, контрольная сумма и цифровая подпись решают разные задачи.

## SHA-256

Контрольная сумма обнаруживает случайное повреждение файла:

Bash:
sha256sum installer.sh

Создание файла с контрольной суммой:

Bash:
sha256sum installer.sh > installer.sh.sha256

Проверка:

Bash:
sha256sum -c installer.sh.sha256

Однако злоумышленник, который может заменить установщик, может одновременно заменить и файл с контрольной суммой.

## Цифровая подпись

Для проверки автора нужна цифровая подпись.

Например, с помощью GnuPG:

Bash:
gpg \
    --armor \
    --detach-sign \
    installer.sh

Получится:

Код:
installer.sh.asc

Проверка:

Bash:
gpg \
    --verify installer.sh.asc \
    installer.sh

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

## Рекомендуемая схема распространения

Публикуются три файла:

Код:
installer.sh
installer.sh.sha256
installer.sh.asc

Пользователь выполняет:

Bash:
sha256sum -c installer.sh.sha256
gpg --verify installer.sh.asc installer.sh

И только после этого запускает:

Bash:
chmod 700 installer.sh
./installer.sh

---

# 11. Безопасная работа с временными файлами

При извлечении полезной нагрузки нельзя использовать предсказуемые пути вроде:

Bash:
/tmp/payload

Другой пользователь может заранее создать файл или символическую ссылку с таким именем.

Правильнее применять:

Bash:
TEMP_DIR="$(mktemp -d)"

Удаление временного каталога:

Bash:
cleanup() {
    rm -rf -- "$TEMP_DIR"
}

trap cleanup EXIT INT TERM

Также полезно установить:

Bash:
umask 077

После этого создаваемые файлы будут по умолчанию доступны только текущему пользователю.

## Безопасный базовый шаблон

Bash:
#!/usr/bin/env bash

set -Eeuo pipefail
umask 077

TEMP_DIR="$(mktemp -d)"

cleanup() {
    rm -rf -- "$TEMP_DIR"
}

trap cleanup EXIT INT TERM

echo "Временный каталог: $TEMP_DIR"

## Зачем нужен set -Eeuo pipefail

Bash:
set -Eeuo pipefail

включает более строгий режим Bash:

  • -e — завершение при ошибке команды;
  • -E — наследование обработчиков ошибок функциями;
  • -u — ошибка при обращении к неопределённой переменной;
  • pipefail — конвейер считается ошибочным, если завершилась с ошибкой любая его команда.

---

# 12. Как исследовать неизвестный самораспаковывающийся скрипт

Нельзя запускать неизвестный .sh- или .run-файл только потому, что он выглядит как установщик.

## Определение типа файла

Bash:
file unknown-installer.run

## Просмотр начала

Bash:
head -n 100 unknown-installer.run

## Поиск маркеров

Bash:
grep -nE \
    'PAYLOAD|ARCHIVE|BEGIN|base64|openssl|age|tar|gzip' \
    unknown-installer.run

## Проверка синтаксиса

Bash:
bash -n unknown-installer.run

Проверка синтаксиса не подтверждает безопасность, но позволяет обнаружить ошибки Bash.

## Статический анализ через ShellCheck

Bash:
shellcheck unknown-installer.run

## Поиск опасных конструкций

Bash:
grep -nE \
    'curl|wget|eval|bash -c|sh -c|sudo|systemctl|crontab|chmod|chown|/etc/|/proc/|/dev/' \
    unknown-installer.run

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

## Извлечение данных без выполнения

Если после маркера расположен Base64:

Bash:
LINE="$(
    awk '/^__ARCHIVE_BELOW__$/ {
        print NR + 1
        exit
    }' unknown-installer.run
)"

tail -n +"$LINE" unknown-installer.run |
    base64 -d > extracted.tar.gz

Просмотр списка файлов без распаковки:

Bash:
tar -tzf extracted.tar.gz

Распаковка:

Bash:
mkdir inspection

tar -xzf extracted.tar.gz \
    -C inspection \
    --no-same-owner \
    --no-same-permissions

После этого:

Bash:
find inspection -type f -print

## Не используйте curl | bash

Опасная конструкция:

Bash:
curl -fsSL https://example.org/install.sh | bash

Она сразу передаёт загруженный код интерпретатору.

Безопаснее:

Bash:
curl -fL \
    -o install.sh \
    https://example.org/install.sh

Затем:

Bash:
sha256sum install.sh
less install.sh
bash -n install.sh
shellcheck install.sh

И только после проверки:

Bash:
chmod 700 install.sh
./install.sh

---

# 13. Что выбрать на практике

## Для небольшого текстового файла

Используйте Here Document:

Bash:
cat > config.ini <<'EOF'
...
EOF

## Для одного бинарного файла

Используйте Base64:

Bash:
base64 file.bin

Но помните, что это кодирование, а не шифрование.

## Для нескольких файлов

Используйте:

Bash:
tar + gzip + Base64

или готовую утилиту Makeself.

## Для автономного Linux-установщика

Наиболее удобный вариант:

Код:
Makeself

Он уже реализует упаковку, распаковку, временный каталог и проверку целостности.

## Для защиты исходника от беглого просмотра

Можно использовать:

Код:
SHC

Но это обфускация, а не надёжное шифрование.

## Для конфиденциальных данных

Используйте:

Код:
age

Предпочтительно шифровать на публичный ключ получателя, а не хранить общий пароль внутри скрипта.

## Для подтверждения автора

Используйте цифровую подпись:

Код:
GnuPG, Minisign или другой инструмент подписи

## Рекомендуемая архитектура

Для серьёзного проекта можно использовать следующую схему:

Код:
Исходные файлы
      │
      ▼
TAR-архив
      │
      ▼
Шифрование через age
      │
      ▼
Встраивание в Shell-установщик
      │
      ▼
Подсчёт SHA-256
      │
      ▼
Цифровая подпись установщика

При запуске:

Код:
Проверка подписи
      │
      ▼
Извлечение зашифрованного контейнера
      │
      ▼
Запрос ключа или пароля
      │
      ▼
Расшифровка во временный каталог
      │
      ▼
Проверка содержимого
      │
      ▼
Явный запуск установки

---

# Заключение

Полезную нагрузку можно разместить непосредственно внутри Shell-скрипта несколькими способами:

  • текст — через Here Document;
  • бинарные данные — через Base64;
  • набор файлов — через TAR-архив;
  • автономный установщик — через Makeself;
  • скрытие исходника — через SHC;
  • криптографическая защита — через age или OpenSSL;
  • подтверждение подлинности — через цифровую подпись.

Главное — не путать Base64, сжатие и обфускацию с настоящим шифрованием.

Надёжный установщик должен:

1. использовать set -Eeuo pipefail;
2. создавать временный каталог через mktemp;
3. удалять временные данные через trap;
4. проверять контрольную сумму;
5. по возможности проверять цифровую подпись;
6. не хранить пароль внутри исходного кода;
7. не выполнять загруженные данные через eval;
8. не использовать конструкцию curl | bash;
9. позволять извлечь и проверить содержимое до запуска;
10. явно сообщать пользователю, какие изменения будут внесены в систему.
 
Назад
Верх Низ