Гарантированная очистка жесткого диска с помощью Perl

  Автор: © Mark Nielsen
Перевод: © Сергей Скороходов.


  Содержание:

  1. Источники
  2. Введение
  3. Проблемы, с которыми я столкнулся
  4. Собственно скрипт
  5. Заключение

Введение

Когда GNUJobs.com переезжал из Огайо в Калифорнию, несколько жестких дисков вместе с другим компьютерным железом планировалось подарить Группе пользователей Linux Центрального Огайо COLUG (Central Ohio Linux Users Group). Перед этим предполагалось стереть на дисках все данные. На 2 из 3 жестких дисков имелись сбойные сектора, а третий я сделал своим "тестовым полигоном" для таких целей, как написание этой статьи, так что все диски в результате остались у меня. Но, поскольку мне все равно предстоит очищать диски в будущем, я написал скрипт на Perl'е (позднее я перепишу его на Python'е и добавлю еще опций).

В задачу скрипта входила простая очистка /dev/hdb (ведомый диск первичного контроллера IDE), т. к. к нему у меня был подключен набор для работы с удаляемыми жесткими дисками. Я планировал удалить все имеющиеся разделы, создать единственный раздел максимального размера и заполнить его всяким мусором, в том числе и обрывками зашифрованных данных (для того, чтобы занять на денек-другой предполагаемого хакера выяснением того, что же было на диске).

Проблемы

Вот как я решал возникающие проблемы:

  1. Как удалить все разделы?

    Я, помнится, исследовал разные варианты переразбиения жесткого диска, и лучшим способом оказалось сделать все вручную. Я уже использовал Perl-скрипт Expect для автоматизации программы fdisk (Linux-утилита для разбиения жестких дисков) раньше и решил использовать этот прием и впредь. Думаю, что есть лучшая альтернатива для такой простой задачи, как удаление всех разделов, скажем, sfdisk или что другое, но если какой-нибудь способ обладает 100% эффективностью и гибкостью, то обычно я его и использую. В этом случае мне не приходится запоминать лишнюю информацию и учиться чему-либо прямо в процессе работы, когда все вдруг оказывается сложнее, чем предполагалось.

    Таким образом, я использовал код Expect для эмулирования нажатий клавиш пользователем при наборе команд fdisk. Этот код удалял все разделы на жестком диске и создавал один большой раздел.

  2. Как я заполнял жесткий диск "мусором"?

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

  3. Как записать на диск двоичные данные для того, чтобы запутать хакера?

    Случайные двоичные данные я получал с помощью функций random и chr из библиотеки Perl. Затем я шифровал их с помощью Perl-модуля Blowfish. Если кому-либо удастся их расшифровать, то к своему изумлению он увидит случайную последовательность символов. Зашифровать данные мне хотелось для того, чтобы они не выглядели совсем уж случайными в математическом смысле.

  4. Как отформатировать большой раздел?

    Это-то как раз просто. Я просто использовал команду mkfs.

Сам скрипт

Для этого скрипта я воспользовался устаревшей версией. У меня был Perl 5.005_03, а к январю 2001 г. доступны версии Perl до 5.6 включительно.

Можно много еще чего сделать, чтобы сделать скрипт удобнее в работе. Учитывая, как много может натворить эта маленькая программка, следовало бы добавить дополнительные проверки для исключения ошибок и запрашивать у пользователя подтверждение тех или иных действий. Но эти усовершенствования я отложил до того момента, когда я возобновлю проект MILAS (который будет написан на Python'е). Этот же скрипт писался с единственной целью помочь мне при переезде из Коламбуса к Заливу (Bay Area).

Код я обильно откомментировал, так что надеюсь, даже новичок в Perl'е сможет понять большую часть того, что я пытался достичь. (Исходный текст можно взять здесь). Версия исходного текста с оригинальными (непереведенными) комментариями находится здесь

#!/usr/bin/perl

##### Предполагается:
#  1.  Убедимся, что мы создаем совершенно новую директорию
#        для монтирования с тем, чтобы избежать проблем с безопасностью,
#        если кто-либо еще из пользователей вошел в систему
# 2.   Использовать функции Perl для выполнения множества системных вызовов
# 3.   Автоматически определяем жесткие и гибкие диски и выполняем действия
#       только над не-примонтированными устройствами
#####

use strict;
use Expect;
use Crypt::Blowfish;

#-----------------------------------------------
my $Junk;
 ### Настроимся на ведомый диск первичного контроллера IDE.
my $Drive = "hdb";

 ### Выполним множество случайных действий,
 ### а в завершение возьмем последнюю строку /etc/passwd
 ### в предположении, что на компьютере был добавлен один пользователь
my $time = time();
my $Ran = rand($time);
my $Ran = rand(10000000000000);
my $LastLine = `tail -n 1 /etc/passwd`; chomp $LastLine;
$LastLine = substr ($LastLine,0,30);
my $Blowfish_Key = $LastLine . $Ran . $time;
$Blowfish_Key = substr ($Blowfish_Key,0,20);
while (length ($Blowfish_Key) < 56)
   {
   $Blowfish_Key .= $Ran = rand($time);
   }
$Blowfish_Key = substr ($Blowfish_Key,0,56);

 ### Случайный ключ готов, создаем объект Blowfish Encryption.
my $Blowfish_Cipher = new Crypt::Blowfish $Blowfish_Key;

#------------------------------------
system "clear";
print "This will wipe out the hard drive on Drive /dev/$Drive\n";
print "Press enter to continue\n";
my $R = <STDIN>;

 ### Получить список смонтированных разделов
my @Mounted = `df`;
@Mounted = grep($_ =~ /\/dev\/hdb/, @Mounted);
 ### Размонтируем смонтированные разделы
foreach my $Mount (@Mounted)
   {
   my ($Partition,$Junk) = split(/\s+/, $Mount,2);
   print "Unmounting $Partition\n";
   my $Result = system ("umount $Partition");
   if ($Result > 0)
      {
      print "ERROR, unable to umount $Partition, aborting Script, Error = $Result\n";
      exit;
      }
   }

 ### Запуск скрипта Expect, который эмулирует выполнение команд вручную
my $Fdisk = Expect->spawn("/sbin/fdisk /dev/$Drive");

 ### Из вывода fdisk'ом таблицы разделов получаем их список
print $Fdisk "p\n";
my $match=$Fdisk->expect(30,"Device Boot Start");

my $Temp = $Fdisk->exp_after();
my @Temp = split(/\n/, $Temp);
 ## Выделяем строки с информацией о разделах
my @Partitions = grep($_ =~ /^\/dev\//, @Temp);
 
 ## Для каждой такой строки -- удаляем раздел
foreach my $Line (reverse @Partitions)
   {
   ## Получаем раздел /dev/hdb и его номер
   my ($Part,$Junk) = split(/[\t ]/, $Line,2);
   my $No = $Part;
   $No =~ s/^\/dev\/$Drive//;
 
   print "Deleting no $Drive $No\n";
 
    ## Команда на удаление
   print $Fdisk "d\n";
   $match=$Fdisk->expect(30,"Partition number");
 
    ## Указываем номер удаляемого раздела
   print $Fdisk "$No\n";
   $match=$Fdisk->expect(30,"Command (m for help):");
   }
 
$Fdisk->clear_accum();
 
  ### Если разделы еще остались -- записываем изменения, если нет -- выходим
if (@Partitions < 1) {print $Fdisk "q\n"; $Fdisk->expect(2,":");}
else
   {
   print $Fdisk "w\n";
   $Fdisk->expect(30,"Command (m for help):");
   }
 
#-------------------------------
  ## Получаем геометрию жесткого диска
my $Geometry = `/sbin/sfdisk -g /dev/$Drive`;
my ($Junk, $Cyl, $Junk2, $Head, $Junk3, $Sector,@Junk) = split(/\s+/,$Geometry);
if ($Cyl < 1)
    {print "ERROR: Unable to figure out cylinders for drive. aborting\n"; exit;}
 
  ### Новый скрипт Expect для эмуляции действий пользователя
my $Fdisk = Expect->spawn("/sbin/fdisk /dev/$Drive");
 
   #### Велим fdisk создать новый раздел
print $Fdisk "n\n";
$Fdisk->expect(5,"primary");
 
  ### Новый раздел будет первичным
print $Fdisk "p\n";
$Fdisk->expect(5,":");
 
  ### Какой раздел? Номер 1
print $Fdisk "1\n";
$Fdisk->expect(5,":");
 
  ### От забора (цилиндра 1)...
print $Fdisk "1\n";
$Fdisk->expect(5,":");
 
  ### ...и до обеда (конца диска)
print $Fdisk "$Cyl\n";
$Fdisk->expect(5,":");
 
  ### Запишем и сохранимся
print $Fdisk "w\n";
$Fdisk->expect(30,"Command (m for help):");
 
#------------------------------------------
### Отформатируем раздел и смонтируем его
 
my $Partition = "/dev/$Drive" . "1";
my $Result = system ("mkfs -t ext2 $Partition");
if ($Result > 0) {print "Error making partition, aborting.\n"; exit;}
 
   ### Тут надо бы добавить всяких проверок...
system "umount /tmp/WIPE_IT";
system "rm -rf /tmp/WIPE_IT";
system "mkdir -p /tmp/WIPE_IT";
system "chmod 700 /tmp/WIPE_IT";
 
  ## Посмотрим, получилось ли смонтировать.
my $Result = system ("mount $Partition /tmp/WIPE_IT");
if ($Result > 0) {print "Error mounting drive, aborting.\n"; exit;}
system "chmod 700 /tmp/WIPE_IT";
 
#--------------------------------
### Создаем файло и пишем до упора.
 
my $Count = 0;
my $Written_Size = 0;
 
  ### Открываем новый файл.
open(FILE,">>/tmp/WIPE_IT/Message.txt");
   ### И если кому-то придет в голову проиграться с нашим винтом,
   ### то пусть разомнутся с этой завлекушечкой, мы тоже любим играться.
my $Ran = rand 259200000;   # эдак между сейчас и 10 лет тому назад...
($Ran, $Junk) = split(/\./, $Ran, 2);
   ## Новая дата минус случайное число секунд...
my $Date = `date --date '-$Ran seconds'`;
 
print FILE "DATE CREATED $Date\n";
my $Ran = rand 50;
($Ran, $Junk) = split(/\./, $Ran, 2);
$Ran = $Ran + 10;
print FILE "This document is extremely secure. It is a violation to let
any unauthorized persons read it. Known password holders need to
apply Method $Ran in order to decrypt binary data.\n";
 
  ### Случайное число плюс 25000
my $Ran = rand 25000;
($Ran, $Junk) = split(/\./, $Ran, 2);
$Ran = $Ran + 25000;
 
  ### Создаем массив случайных чисел для частого употребления.
my @Blank =  (1..$Ran);
  ### Превращаем его в строку.
my $Blank = "@Blank";
  ### Освободим массив для экономии памяти.
@Blank = ();
my $B_Length = length $Blank;
 
  ### Получаем доступное место на разделе
my @Temp = `df`;
@Temp = grep($_ =~ /^$Partition/, @Temp);
my $Line = $Temp[0];
my ($Junk,$Blocks,@Junk) = split(/\s+/, $Line,4);
  ### Предполагаем, что блок равен 1k.
my $Size = $Blocks*1000;
 
  ## Если записанный файл меньше размера раздела
  ## запишем еще немного.
while ($Written_Size < $Size)
  {
  $Count++;
 
        ### 9 раз из десяти печатаем пустые данные для ускорения процесса
        ### а один раз печатаем цифровой мусор.
     my $Ran = rand (10);
     if ($Ran > 1)
       {
       print FILE $Blank;
       $Written_Size = $Written_Size + $B_Length;
       }
     else
       {
         ## А здесь сделаем длинную (до 10000 байт) строку случайных символов.
       my $Garbage = "";
       my $Length = rand(10000);
       ($Length, $Junk) = split(/\./, $Length, 2);
       for (my $i = 0; $i < $Length; $i++)
         {
         my $Ran = rand 256;
         ($Ran, $Junk) = split(/\./, $Ran, 2);
         $Garbage .= chr $Ran;
         }
         ## Здесь зашифруем случайную строку по 8 байт за раз.
       my $Temp = $Garbage;
       my $Encrypted = "";
       while (length $Temp > 0)
         {
         while (length $Temp < 8) {$Temp .= "\t";}
         my $Temp2 = $Blowfish_Cipher->encrypt(substr($Temp,0,8));
         $Encrypted .= $Temp2;
         if (length $Temp > 8) {$Temp = substr($Temp,8);} else {$Temp = "";}
         }
 
         ### Запишем зашифрованные случайные данные в файл.
       print FILE $Encrypted;
       $Length = length $Encrypted;
 
       $Written_Size = $Written_Size + $Length;
       my $Rest = $Size - $Written_Size;
       print "$Size - $Written_Size = $Rest to go\n";
       }
 
   ### Каждые 500 вызовов print начинаем новый файл.
  if ($Count =~ /500$/)
    {
    close FILE;
    open(FILE,">>/tmp/WIPE_IT/$Count");
    }
  }
 
close FILE;
#----------------------------------------------------
 
my $Result = system ("umount $Partition");
if ($Result > 0) {print "Error unmounting partition $Partition, aborting.\n"; exit; }
 
  ### Отформатируем раздел. Это не сотрет данные,
  ### а просто удалит их из каталога.
my $Result = system ("mkfs -t ext2 $Partition");
if ($Result > 0) {print "Error making partition, aborting.\n"; exit;}
 

Заключение

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

Я не понимаю жесткие диски во всей их сложности, так что я не уверен в том, что после моей процедуры на диске совсем не останется следов от данных. Для моих целей (и на моем уровне требований к безопасности), скрипт делает в точности то, что мне нужно. По мере дальнейшего развития MILAS наверняка добавятся более жесткие проверки и усовершенствования для того, чтобы все данные на диске были уничтожены.

Я склонен заглядывать вперед, и стараюсь предугадать то, что может понадобиться в будущем, а это всегда заставляет программиста работать над своим проектом больше, чем продиктовано непосредственной необходимостью. Однако я был в настроении, мне нравилось направление, в котором развивается скрипт, и меня не затруднило написать эту статью, пока я летел в самолете. Работа над какой-нибудь крутой штуковиной меня не утомляет, в отличие от необходимости сделать работу для кого-то другого (это-то и есть настоящая работа).

Источники

  1. Perl.com website.
  2. Expect Perl Module
  3. Blowfish Perl Module
  4. Original site for this article. - http://www.gnujobs.com/Articles/14/Wipe_It.html (в случае внесения изменений в статью, новая версия появится именно здесь)

 


Copyright © 2001, Mark Nielsen.
Copying license http://www.linuxgazette.com/copying.html
Published in Issue 63 of Linux Gazette, Mid-February (EXTRA) 2001

Вернуться на главную страницу