Linux с двойным дном

6bc530e7a49d103052dda391e2bd7c02

Кулхацкеры всех стран — соединяйтесь!

Если у вас паранойя, это не значит, что за вами не следят.

В этой статье я расскажу как сделать так, чтобы ваша линуксовая машинка выглядела невинной игрушкой, но при вводе нескольких команд превращалась в настоящую боевую единицу. Конечно, у вас могут найти на диске сектора с необычно высокой энтропией, несколько подозрительных системных настроек, но никаких явных зашифрованных разделов, файлов, или сторонних шифровалок. Конечно, вас могут спросить — «а для чего тебе cryptsetup, сынок?», на что вы ответите — «это же Linux Mint, это всё искаропки!» Хуже, если бы вас спросили: зачем ты используешь LUKS, или, что ещё хуже, зачем ты поставил VeraCrypt или Shufflecake.

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

Главное в системе с двойным дном — это, конечно же, секретные зашифрованные разделы, которые нигде не отсвечивают. Мой способ — это cryptsetup поверх losetup. Особенность losetup в том, что он может создать устройство в любом месте — даже на примонтированной файловой системе, и при этом не станет возмущаться, как dm, что устройство уже используется. Способ не работает на слабых машинках с диском, подключенным через USB, таких как, например, NanoPI Zero, у которых 32-битный Allwinner H3, но начиная с 64-битных H6 или Rockchip RK3328 и выше, а тем более на всяких ваших Intel и AMD никаких проблем я не наблюдаю уже несколько лет.

Но! Проблемы появляются, если объединить много loop устройств в одно с помощью dm. То есть, использовать все более-менее крупные свободные блоки файловой системы ext4. Даже мой i3 такое не вытянул. Я не помню точно, какие ошибки были в dmesg, но что-то связанное с асинхронной обработкой внутри ядра. На слабеньких 32-битных Allwinner — то же самое даже с одним loop. Глубоко в проблему я не нырял, просто перестал использовать проблемный подход.

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

Ext4 гораздо хуже, там сильная фрагментация свободного места, тем не менее, для небольших оверлейных разделов /etc, /var, и т.п. вполне годится.

Вы, конечно, спросите, а как найти нетронутое свободное место на просторах файловой системы, да и вообще на диске? Возможно, есть готовое решение, но для себя я всё привык делать сам, и у меня для этого есть несложный скрипт. Идея простая: заполняем весь диск случайными данными из /dev/urandom, вычисляем и сохраняем в файле хэши секторов, устанавливаем систему, а потом снова вычисляем хэши, смотрим, какие сектора изменились, и таким образом находим нетронутые блоки.

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

Тут, конечно, стоит упомянуть о маркерах. В алгоритмах шифрования могут быть намеренные изъяны, которые приводят к определённым сигнатурам в зашифрованных данных. То есть, вы можете скачать какую-нибудь порнуху на зашифрованный раздел и на этом попасться. Так что, поаккуратнее. Защититься от этого можно двойным шифрованием, cryptsetup поверх cryptsetup, желательно разными алгоритмами. При необходимости вы сами сможете запросто это реализовать.

Готовимся шифроваться

Вам понадобится мой тулкит, который автоматизирует всю рутину. Скачайте его, только сразу на флешку, которую, впоследствии надо будет хорошенько затереть из /dev/urandom. Следов оставлять нельзя.

Вы сейчас наверняка используете незащищённую систему. Не лезьте на мой гитхаб из браузера — это осядет в истории и будет работать против вас. То, что вы читаете эту статью, ещё можно объяснить любопытством, но скачивание тулкита — это уже намерение и +1 к подозрениям. Допустим, флешка у вас примонтирована в /mnt/flash. Первая команда — отключение записи истории (если у вас не bash, используйте свои методы):

HISTFILE=
swapoff
cd /mnt/flash
git clone git@github.com:amateur80lvl/pdt.git

Инициализируем диск (к примеру, /dev/sdc) случайными данными:

dd if=/dev/urandom of=/dev/sdc bs=4K status=progress

и вычисляем хэши секторов:

python3 secha.py compute /dev/sdc original-sector-hashes

original-sector-hashes — это имя файла для хэшей. Для экономии места я использовал 64-битный blake2s, с которым я не наблюдал коллизий на 128GB SSD — лично мне больше и не надо. Для анализа диска размер хэша не особо критичен, но с 48 битным коллизии уже наблюдаются. Я их искал простой программкой на C++ (на питоне такое не напишешь — сожрёт всю память). Для верности, конечно, можете подкрутить размер хэша до 96 или 128 бит, поправив исходник secha.py.

Подготовка диска

Сразу устанавливать свой любимый Linux не надо! Инсталляторы, как правило, тупые до безобразия, да и TRIM везде включен по умолчанию. С ним все ваши хэши окажутся бесполезны. Разбивку диска и форматирование разделов надо провести заранее, а инсталятору сказать, чтобы ничего не трогал и ставил куда скажут.

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

Да, не забываем про UEFI раздел, если нужен.

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

TRIM надо отключить обязательно. Сначала на этапе форматирования раздела опцией nodiscard:

mkfs -t ext4 -E nodiscard /dev/sdc1

А потом периодический TRIM, сразу после установки. То есть, надо будет сделать что-то подобное:

systemctl disable fstrim.timer

Отключение TRIM — это палево. Запишите его в столбец рисков. Способ обойтись без этого только один — использовать старые добрые жужжащие HDD. Интернеты рекомендуют выключить TRIM и для них, но по крайней мере это обосновано и вопросов не вызывает.

Установка Linux

Берёте свой любимый дистрибутив, в котором cryptsetup уже есть по умолчанию — и устанавливаете на предварительно отформатированный раздел. Linux Mint в этом отношении идеален, насчёт других ничего не знаю. Ну, кроме Armbian, разве что. В Debian netinstall cryptsetup по умолчанию отсутствует.

Когда система установлена, выключаем комп, достаём диск для анализов, или загружаем какой-нибудь live дистрибутив с флешки и смотрим, какие области у нас остались нетронутыми:

python3 secha.py find-intact /dev/sdc original-sector-hashes

В принципе, чтобы не анализировать весь диск и пропустить системный раздел, можно задать начальный и конечный секторы и размер сектора, который по умолчанию 512. Эти параметры должны быть одинаковами для secha.py compute и secha.py find-intact.

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

Следующий обязательный шаг — настроить использование tmpfs для /tmp и /var/log. В логах, например, можно увидеть факт использования cryptsetup, ну, а в /tmp тоже может утечь всякое. Добавляем в /etc/fstab:

tmpfs  /tmp      tmpfs  nosuid,nodev,mode=1755,size=32M       0 1
tmpfs  /var/log  tmpfs  nosuid,noexec,nodev,mode=755,size=4M  0 1

Размер можете указать на свой вкус.

Эти строчки в fstab — тоже палево. Отметьте у себя.

Установка тулкита

Нам понадобится маленький секретный раздел для конфигурации и тулкита, который бутстрапит систему. Достаточно 360K, как пятидюймовая дискета, но на самом деле размер должен быть такой, который лучше запоминается и выровнен по границе сектора. Самое простое — 512000. Где его расположить — ваша забота. Я могу рекомендовать начало диска, сразу после GPT. Там, начиная от сектора 34 и до начала первого раздела обычно есть по крайней мере один свободный мегабайт. Можно разместить его в конце диска, только имейте ввиду, что там, в последних 34 секторах, находится копия GPT. В случае с MBR проще, — занят только первый сектор диска. А ещё можно оставить дырку между разделами, но тогда у стороннего наблюдателя возникнет вопрос — зачем?

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

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

Допустим, размер сектора у нас 512 байт и наш бутстрапный раздел будет находиться начиная с сектора 40 и занимать следующие 1000 секторов. Умножаем номера на 512 и создаём loop устройство:

losetup --offset 20480 --sizelimit 512000 -f /dev/sda

Кстати, историю команд не забыли отключить? Можно это сделать перманентно в .bashrc или что вы там используете вместо него, но это +1 к подозрениям.

Следующая команда открывает шифрованный раздел:

cryptsetup open /dev/loop0 bootstrap -y --type plain

Вводим свой заковыристый бутстрапный пароль.

Далее форматируем раздел:

mkfs -t ext2 /dev/mapper/bootstrap

Создать ext4 на таком маленьком разделе у вас не получится, поэтому используем ext2. Можно и fat, конечно же, но я не пробовал.

Советую проверить, в состоянии ли вы ввести пароль заново. Закрываем шифрованный раздел и пробуем открыть его заново:

cryptsetup close bootstrap
cryptsetup open /dev/loop0 bootstrap --type plain

Теперь смотрим, показывает ли blkid UUID для /dev/mapper/bootstrap. Если нет, вы ввели пароль неправильно. Попробуйте переоткрыть раздел заново. Если ничто не помогает — начинаем снова с команды форматирования. Повторяем, пока успех не настигнет вас окончательно.

Получилось? Тогда создаём точку монтирования в /tmp и монтируем:

mkdir /mnt/bootstrap
mount /dev/mapper/bootstrap /mnt/bootstrap

Копируем файлы тулкита с флешки на бутстрапный раздел, а флешку тщательно затираем.

Конфигурация тулкита

Все наши секретные разделы устроены точно так же, и тулкит автоматизирует процесс, что был описан выше. Нам надо подготовить файл конфигурации в формате JSON, который выглядит примерно так:

{
    "devices": {
        "S23SNEAG516433Y": "ssd",
        "WD-WX61BA51RF6A": "hdd"
    },
    "volumes": {
        "my-data": {
            "device": "ssd",
            "start":  "128664014 * 512",
            "end":   "(((488397168 - 34) * 512) // 4096) * 4096",
            "sector_size":  4096,
            "key": "KbQbEe9XZ4hPbYEWzZ3XZlbydGnkV0yLCoPSZVIP0cgyxTYC",
            "mount_point": "/mnt/my-data"
        },
        "my-archive": {
            "device": "hdd",
            "start":  "838186828 * 512",
            "end":   "((1465149168 * 512) // 4096) * 4096",
            "sector_size":  4096,
            "key": "DtgVIyikwY5rTUvmQwFpzfm6Fze2TvaV9iLbWp2W5eps64TF",
            "mount_point": "/mnt/my-archive"
        }
    },
    "containers": [
        "devapps",
        "safedns"
    ]
}

Секция devices определяет имена устройств по их серийным номерам. Эти номера можно посмотреть командой

lsblk -d -o NAME,SERIAL

В нашем примере у нас в системе два диска и в секции devices мы говорим, что диск с серийным номером S23SNEAG516433Y у нас будет называться ssd, а второй — hdd. Неважно, какие имена для них вы придумаете.

Секция volumes определяет наши секретные разделы — тома. В нашем случае том my-data у нас расположен на устройстве с именем ssd, а том my-archive — на hdd.

Параметры start и end определяют начальную и конечную позицию раздела в байтах, и могут быть как числами, так и строками-формулами, чтобы не считать байтовые позиции вручную. Например, формула параметра end для раздела my-data вычисляет конечную позицию раздела исходя из размера диска 488397168 секторов по 512 байт минус 34 сектора копии GPT, и выравнивает её по границе 4096-байтного сектора для loop-устройства. Размеры секторов не обязательно должны совпадать. В нашем примере везде используются 4096-байтные сектора, хотя сектора физических устройств — 512 байт.

Параметр key задаёт пароль для losetup и может быть сгенерирован скриптом pdt_genkey.

Вы уже определились, где будете создавать свои секретные разделы? Тогда создадим каталог конфигурации для нашего компа — так удобнее, чтобы не мешать файлы в кучу. К тому же, вы можете добавить каталоги для других машин и бутстрапить их по SSH.

mkdir /mnt/bootstrap/my-hidden-system

Создаём файл конфигурации:

nano /mnt/bootstrap/my-hidden-system/config.json

Ну или vi, если вам так привычнее. Ах да, забыл упомянуть про секцию containers — это для запуска секретных LXC контейнеров. Если они у вас будут, конечно же.

Создание разделов

Ну что, написали свою конфигурацию? Вряд-ли у вас получится идеально с первого раза, но пока сгодится любой вариант. Потом отполируете, через несколько переустановок.

Итак, конфигурация есть, — создаём тома. Кстати, давайте перейдём в каталог /mnt/bootstrap чтобы не писать его каждый раз:

cd /mnt/bootstrap

Для нашего примера команды будут такими:

./pdt_create_volume my-hidden-system my-data
./pdt_create_volume my-hidden-system my-archive

В общем случае команда создания тома выглядит так:

pdt_create_volume config-dir volume-name [remote-hostname]

Но это вы уже и сами поняли, когда провели аудит кода. Не так ли?

Секретная система

Очень многие компоненты оставляют следы в самых неожиданных местах, и лучше в базовой системе не работать вообще, а использовать, к примеру, VirtualBox для запуска секретной системы с секретного раздела. Однако, изначально тулкит использовался на ARM машинках, где ни VirtualBox, ни KVM не работали в принципе. Не знаю как сейчас обстоят дела, тем не менее, некоторые каталоги базовой системы надо перемонтировать, чтобы перекрыть возможные утечки от того же VirtualBox, который обязательно нагадит в ваш домашний каталог.

Нам нужно подменить следующие каталоги:

  • в /etc монтируем overlayfs чтобы скрыть дополнительных пользователей, ну и всякую другую конфигурацию, которой в нормальной системе не должно быть. Исключение — /etc/apt, его подмонтируем заново через bind. Вообще, в /etc накапливается много лишних изменений, которые время от времени приходится мержить вручную. Я это делал каждый раз при обновлении системы.

  • в /home монтируем tmpfs и в неё уже монтируем через bind оригинальные пользовательские каталоги, плюс каталоги дополнительных пользователей с секретного раздела. Либо, можно смонтировать в /home каталог с секретного раздела и уже в него через bind — оригинальные пользовательские каталоги.

  • в /root через bind монтируем каталог с секретного раздела. Обычно я там держал совершенно другие ключи для SSH.

  • также, overlayfs используем для /var/tmp и /usr/local

  • если используется LXC, то в /var/lib/lxc монтируем каталог с секретного раздела

Соответственно, вам надо создать все необходимые каталоги на секретных разделах. А потом подготовить bootstrap-скрипт. Вот пример скрипта для маленького сервера с секретным разделом в /mnt/hidden, который бутстрапится через SSH:

#!/usr/bin/env python3

import os
base_dir = os.path.dirname(os.path.abspath(__file__))

import sys
sys.path.insert(0, os.path.dirname(base_dir))

from pdt_base import read_config, setup, Invoke
from pdt_tasks import *

# читаем конфигурацию и создаём экземпляр Invoke
config = read_config(base_dir)
invoke = Invoke(remote='myserver')
invoke.set_devices(config)  # обрабатываем секцию devices

invoke.run('systemctl stop nfs-kernel-server')

setup(
    config, invoke,

    TmpfsMounts('/mnt'),  # монтируем tmpfs в /mnt
    MountRoot,            # монтируем оригинальную корневую
                          #   файловую систему в /mnt/root
    MountVolumes,         # монтируем все тома
    BindMounts(           # заменяем /root
        ('/mnt/hidden/root', '/root')
    )
)

# ключи для SSH для доступа к серверу поменялись
# после замены /root, надо пересоздать экземпляр Invoke
invoke = Invoke(remote='myserver', ssh_key='~/.ssh/secret_id_ecdsa')

# продолжаем
setup(
    config, invoke,

    # перемонтируем в overlayfs следующие каталоги:
    OverlayMounts(
        ('/mnt/root/etc',       '/mnt/hidden/etc',       '/mnt/hidden/etc.workdir',       '/etc'),
        ('/mnt/root/var/tmp',   '/mnt/hidden/var/tmp',   '/mnt/hidden/var/tmp.workdir',   '/var/tmp'),
        ('/mnt/root/usr/local', '/mnt/hidden/usr/local', '/mnt/hidden/usr/local.workdir', '/usr/local')
    ),
    BindMounts(
        # секретные контейнеры LXC
        ('/mnt/hidden/lxc',   '/var/lib/lxc'),
        # восстанавливаем некоторые перекрытые каталоги
        ('/mnt/root/etc/apt', '/etc/apt')
    )
)

# из-за изменений в /etc надо:
invoke.run('systemctl daemon-reexec')
invoke.run('systemctl start nfs-kernel-server')

# запускаем секретные контейнеры LXC
for container_name in config['containers']:
    invoke.run(f'lxc-start {container_name}')

Но вы же наверняка хотите десктоп, поэтому создаём скрипт в нашем каталоге:

nano my-hidden-system/bootstrap

Вот как он может выглядеть:

#!/usr/bin/env python3

import os
base_dir = os.path.dirname(os.path.abspath(__file__))

import sys
sys.path.insert(0, os.path.dirname(base_dir))

from pdt_base import read_config, setup, Invoke
from pdt_tasks import *

# можем бутстрапить наш комп этим скриптом по SSH:
if len(sys.argv) > 1:
    remote = sys.argv[1]
else:
    remote = None

# читаем конфигурацию и создаём экземпляр Invoke
config = read_config(base_dir)
invoke = Invoke(remote=remote)
invoke.set_devices(config)  # обрабатываем секцию devices

setup(
    config, invoke,

    TmpfsMounts('/mnt'),  # монтируем tmpfs в /mnt
    MountRoot,            # монтируем оригинальную корневую
                          #   файловую систему в /mnt/root
    MountVolumes,         # монтируем все тома
    TmpfsMounts(          # перемонтируем в tmpfs эти каталоги:
        '/home',
        '/var/lib/lxc'
    ),
    BindMounts(           # заменяем /root
        ('/mnt/my-data/root', '/root')
    )
)

if remote:
    # ключи для SSH поменялись после замены /root,
    # поэтому надо пересоздать экземпляр Invoke
    invoke = Invoke(remote=remote, ssh_key='~/.ssh/secret_id_ecdsa')

# продолжаем
setup(
    config, invoke,

    # добавляем секретных пользователей в /home
    BindMounts(
        ('/mnt/my-data/work',     '/home/work'),
        ('/mnt/my-data/browsing', '/home/browsing'),
        ('/mnt/my-data/sans-vpn', '/home/sans-vpn')
    ),
    # перемонтируем в overlayfs следующие каталоги:
    OverlayMounts(
        ('/mnt/root/etc',       '/mnt/my-data/etc',       '/mnt/my-data/etc.workdir',       '/etc'),
        ('/mnt/root/var/tmp',   '/mnt/my-data/var/tmp',   '/mnt/my-data/var/tmp.workdir',   '/var/tmp'),
        ('/mnt/root/usr/local', '/mnt/my-data/usr/local', '/mnt/my-data/usr/local.workdir', '/usr/local')
    ),
    # восстанавливаем некоторые перекрытые каталоги
    BindMounts(
        ('/mnt/root/etc/apt',   '/etc/apt'),
        # допустим, в базовой системе у нас только один пользователь user
        # - восстанавливаем его каталог в /home
        ('/mnt/root/home/user', '/home/user')
    ),
    # перезапускаем сервисы
    RestartServices(
        'syslog',
        'systemd-journald',
        'autofs',
        'display-manager'
    )
)

# создаём необходимые каталоги в /mnt, который теперь tmpfs
invoke.run('mkdir -p /mnt/myserver')
invoke.run('mkdir -p /mnt/usb')
invoke.run('mkdir -p /mnt/temp')

# если в секретной системе другой /etc/nftables.conf, то надо:
invoke.run('systemctl restart nftables')

# а если есть секретная конфигурация для VPN, то:
invoke.run('ip link add dev wg0 type wireguard')
invoke.run('ip address add 10.0.0.2 dev wg0 peer 10.0.0.1')
invoke.run('/bin/bash -c "wg setconf wg0 <(wg-quick strip /etc/wireguard/wg0.conf)"', shell=True)
invoke.run('ip link set up dev wg0')
invoke.run('ip route replace default dev wg0')
# (лично я не использую wg-quick, своим современным подходом
# с fwmark он ломает мне всю маршрутизацию)

# добваляем маршрут к VPN серверу
invoke.run('ip route add 1.2.3.4 via 192.168.0.1')

# для пользователя sans-vpn VPN не нужен (uid, к примеру, 1003):
invoke.run('ip rule add uidrange 1003-1003 table 1 pref 100')
invoke.run('ip route add default via 192.168.0.1 table 1')

# размонтируем бутстрапный раздел:
if not remote:
    invoke.locrypt_unmount('/tmp/bootstrap')

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

chmod +x my-hidden-system/bootstrap

Запускаем, создаём пользователей с помощью adduser или useradd, перезапускаем display-manager. Надеюсь, у вас всё получилось?

Таким образом, вы можете запустить свои иксы для каждого секретного пользователя и переключаться между ними по ALT-F7, ALT-F8, и т.д. Не забывайте проявлять активность в основном, несекретном аккаунте. Котиков иногда разглядывайте, что-ли.

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

HISTFILE=
losetup --offset 20480 --sizelimit 512000 -f /dev/sda
cryptsetup open /dev/loop0 bootstrap --type plain
mkdir /mnt/bootstrap
mount /dev/mapper/bootstrap /mnt/bootstrap
cd /
/mnt/bootstrap/my-hidden-system/bootstrap

Это если вы не придумали ничего лучшего. Я тоже пока нет.

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

© Habrahabr.ru