"Linux Gazette...haciendo linux un poco más divertido!"


Borrando de manera segura un disco rígido con Perl

Por Mark Nielsen

Traducción al español por: Walter Zein
el día 21 de Agosto 2001, para La Gaceta de Linux

Contents

  1. Introducción

  2. Que problemas tengo

  3. El script Perl

  4. Conclusión

  5. Referencias

Introducción

Cuando me mudaba de Ohio a California, GNUJobs.com tenía algunos discos rígidos (junto con otro hardware y software) que iba a ser donado a COLUG (Central Ohio Linux Users Group). Se necesitaba borrarlos antes de donarlos. 2 de los 3 discos rígidos tenían sectores malos en ellos, y el tercero termine usándolo para pruebas, como crear este artículo, así que al final no doné ninguno. Aún así, voy a necesitar borrar un disco rígido en el futuro, de modo que creé este script de Perl (que más tarde convertiría en Python y le agregaría más opciones).

El objetivo de este script Perl es borrar el disco rígido en /dev/hdb (el disco esclavo en el controlador IDE pimario) ya que allí tengo un kit de disco rígido removible. Quiero que borre todas las particiones, que cree una partición que ocupe todo el disco, y luego llene el disco con datos inútiles (incluyendo algunos datos aleatorios encriptados sólo para arruinarle el día a un hacker que trate de descubrir que son esos datos).

Los Problemas

Aquí hay una lista de problemas que tuve y cómo los solucioné:

  1. ¿Cómo hago que borre todas las particiones?

    Recuerdo haber investigado muchas opciones diferentes para alterar particiones en un disco rígido y hacerlo manuaImente me dió los mejores resultados. He usado el script Expect de Perl para automatizar el programa fdisk (fdisk hace particiones de disco rígido en Linux) anteriormente y decidí seguir haciéndolo de esa manera. Creo que hay mejores alternativas para la simple tarea de borrar todas las particiones, como sfdisk y otras, pero si una solución cubre todas las posibilidades con 100% de potencia y flexibilidad, usualmente me inclino a favor de un modo de hacer las cosas tal que no tenga que recordar demasiadas cosas y si se llegara a complicar, no tener que aprender nada nuevo.

    Así, usé el código de Expect para simular un usuario tipeando los comandos de fdisk. El código de expect borró todas las particiones y luego creó una partición grande.

  2. ¿Cómo lleno el disco con datos inútiles?

    Borrar las particiones no es suficiente para eliminar los datos. Quiero sobreescribir los datos viejos con basura para asegurarme de que los datos previos sean borrados. Usé sfdisk para obtener el tamaño de la partición que fdisk creó. Luego hice un bucle que escribiría basura hasta que la cantidad escrita fuera igual o superior al tamaño de la partición.

  3. ¿Cómo pongo datos binarios en el disco rígido para confundir a un hacker?

    Generé datos binarios aleatorios usando la función random y la función "chr" en Perl. Luego, encripté los datos aleatorios usando el módulo Blowfish de Perl. Si alguien se las ingenia para desencriptar los datos, igual se verá como basura y lo confundirá. Yo quería encriptar los datos de manera que no se vieran puramente aleatorios en sentido matemático.

  4. ¿Cómo re-formateo la partición grande?

    Esto fue fácil. Sólo usé un simple comando "mkfs".

El Script Perl

La versión de Perl que estaba usando para este script estaba obsoleta. Yo estaba usando Perl 5.005_03 y creo que Perl llego a la versión 5.6 a enero de 2001.

Hay un montón de cosas que necesito mejorar para hacer este script más amigable al usuario. Debería haber mucho más chequeo de errores, considerando lo peligroso que es este script, y diálogos para preguntar al usuario si realmente quieren hacer esto. Estoy esperando a reiniciar mi proyecto MILAS (que estará escrito en Python) antes de mejorar este script. Sólo fué para apurar mi traslado de Columbus a la Bay Area.

He comentado gran parte del código, así que espero que un programador novato en Perl pueda entender la mayoría de lo que estoy tratando de hacer. (Versión de texto de este listado)

#!/usr/bin/perl

##### Cosas por hacer
# 1. Asegurarse de crear un directorio nuevo de montaje temporario 
#    para evitar riesgos de seguridad en caso de que alguien esté conectado
# 2. Usar funciones para manipular muchas de las llamadas del sistema. 
# 3. Permitirle autodetectar discos rígidos y flexibles, y sólo ejecutar
#    acciones en discos rígidos y flexibles no montados.
##### 

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

#-----------------------------------------------
my $Junk;
  ### Definir el disco a usar como el esclavo del controlador IDE Primario.
my $Drive = "hdb";

  ### Creemos un montón de material aleatorio y obtengamos la última línea del
  ### archivo /etc/passwd para hacerlo bien aleatorio, asumiendo que una persona
  ### ha sido añadida a la computadora. 
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);

  ### Ya está lista la clave aleatoria, ahora crea el objeto Blowfish de Encripción.
my $Blowfish_Cipher = new Crypt::Blowfish $Blowfish_Key;

#------------------------------------
system "clear";
print "Esto borrará el disco rígido en /dev/$Drive\n";
print "Presiona enter para continuar\n";
my $R = <STDIN>;

  ### Obtiene la lista de particiones montadas en el disco que queremos borrar
my @Mounted = `df`;
@Mounted = grep($_ =~ /\/dev\/hdb/, @Mounted);
  ### Foreach (por cada) partición montada, desmóntala
foreach my $Mount (@Mounted)
  {
  my ($Partition,$Junk) = split(/\s+/, $Mount,2);
  print "Desmontando  $Partition\n";
  my $Result = system ("umount $Partition");
  if ($Result > 0) 
    {
    print "ERROR, no puedo desmontar $Partition, abortando script, Error = $Result\n";
    exit;
    }
  }

  ### Comienza el script expect, que simulará hacer estos
  ### comandos manualmente.
my $Fdisk = Expect->spawn("/sbin/fdisk /dev/$Drive");

  ### Obtiene una lista de particiones montadas imprimiendo la tabla de partición 
print $Fdisk "p\n";
my $match=$Fdisk->expect(30,"Device Boot    Start");

my $Temp = $Fdisk->exp_after();
my @Temp = split(/\n/, $Temp);
  ## Obtiene las líneas que nos informan sobre las particiones
my @Partitions = grep($_ =~ /^\/dev\//, @Temp);

  ## Por cada línea, borra la partición
foreach my $Line (reverse @Partitions)
  {
    ## Obtiene la part /dev/hdb y su número
  my ($Part,$Junk) = split(/[\t ]/, $Line,2);
  my $No = $Part;
  $No =~ s/^\/dev\/$Drive//;

  print "Borrando no $Drive $No\n";     

    ## Comando borrar
  print $Fdisk "d\n";    
  $match=$Fdisk->expect(30,"Partition number");
   
    ## Que número de partición borrar
  print $Fdisk "$No\n";
  $match=$Fdisk->expect(30,"Command (m for help):");
  }

$Fdisk->clear_accum();

  ### Si teníamos particiones, escribir los cambios, sinó terminar
if (@Partitions < 1) {print $Fdisk "q\n"; $Fdisk->expect(2,":");}
else 
  {
  print $Fdisk "w\n";
  $Fdisk->expect(30,"Command (m for help):");
  }

#-------------------------------
  ## Obtener la geometría del disco rígido
my $Geometry = `/sbin/sfdisk -g /dev/$Drive`;
my ($Junk, $Cyl, $Junk2, $Head, $Junk3, $Sector,@Junk) = split(/\s+/,$Geometry);
if ($Cyl < 1)
   {print "ERROR: Imposible detectar cantidad de cilindros del disco. Abortando\n"; exit;}

  ### Crear un nuevo script expect para simular una persona usando el fdisk
my $Fdisk = Expect->spawn("/sbin/fdisk /dev/$Drive");

   #### Pedir a fdisk que cree una partición nueva
print $Fdisk "n\n";
$Fdisk->expect(5,"primary");

  ### Decir si la nueva partición es primaria
print $Fdisk "p\n";
$Fdisk->expect(5,":");

  ### Que partición, número 1
print $Fdisk "1\n";
$Fdisk->expect(5,":");

  ### Comenzar en el cilindro 1
print $Fdisk "1\n";
$Fdisk->expect(5,":");

  ### Ir al final
print $Fdisk "$Cyl\n";
$Fdisk->expect(5,":");

  ### Escribir y guardar
print $Fdisk "w\n";
$Fdisk->expect(30,"Command (m for help):");

#------------------------------------------
### Formatear la partición y montarla

my $Partition = "/dev/$Drive" . "1";
my $Result = system ("mkfs -t ext2 $Partition");
if ($Result > 0) {print "Error con la partición, abortando.\n"; exit;}

   ### Aquí debería haber mejor control de errores
system "umount /tmp/WIPE_IT";
system "rm -rf /tmp/WIPE_IT";
system "mkdir -p /tmp/WIPE_IT";
system "chmod 700 /tmp/WIPE_IT";

  ## Ver si podemos montar la nueva partición.
my $Result = system ("mount $Partition /tmp/WIPE_IT");
if ($Result > 0) {print "Error al montar el disco, abortando.\n"; exit;}
system "chmod 700 /tmp/WIPE_IT";

#--------------------------------
### Ahora crear el archivo y detenerse al llegar al tamaño.

my $Count = 0;
my $Written_Size = 0;

  ### Abrir un nuevo archivo.
open(FILE,">>/tmp/WIPE_IT/Message.txt");
   ### Si alguien actualmente quiere molestar con tu disco rígido,
   ### juguemos con él y hagámosle perder tiempo con un rompecabezas.
my $Ran = rand 259200000;   # entre ahora y hace diez años (aprox)
($Ran, $Junk) = split(/\./, $Ran, 2);
   ## Nueva fecha menos cantidad aleatoria de segundos
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 "Este documento es extremadamente seguro. Permitir a personas no autorizadas su lectura es una
violación. Los usuarios poseedores de passwords conocidos necesitarán aplicar
el método $Ran para desencriptar los datos binarios.\n";

  ### Crear número aleatorio más 25000
my $Ran = rand 25000;
($Ran, $Junk) = split(/\./, $Ran, 2);
$Ran = $Ran + 25000;

  ### Crear un array de números que usaremos la mayor parte del tiempo.
my @Blank =  (1..$Ran);
  ### Tomar el array y convertirlo en cadena.
my $Blank = "@Blank";
  ### Vaciar el array para liberar memoria.
@Blank = ();
my $B_Length = length $Blank;

  ### Obtengamos la cantidad real de espacio que tenemos para la partición
my @Temp = `df`;
@Temp = grep($_ =~ /^$Partition/, @Temp);
my $Line = $Temp[0];
my ($Junk,$Blocks,@Junk) = split(/\s+/, $Line,4);
  ### Asumimos bloques de 1k.
my $Size = $Blocks*1000;

  ## Mientras el archivo que hemos escrito sea menor que el tamaño de la
  ## partición, imprimir algunos datos.
while ($Written_Size < $Size)
  {
  $Count++;

        ### 9 de cada 10 veces, queremos escribir sólo espacios para acelerar
        ### la impresión. Una de cada diez veces escribimos basura binaria.
     my $Ran = rand (10);
     if ($Ran > 1)
       {
       print FILE $Blank;
       $Written_Size = $Written_Size + $B_Length;
       }
     else
       {
         ## Esta parte crea una larga cadena (hasta 10000 bytes) de datos aleatorios.
       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;
         }
         ## Estas partes encriptan los datos aleatorios de a 8 bytes por vez.
       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 = "";}
         }

         ### Escribir los datos aleatorios encriptados en archivo.
       print FILE $Encrypted;
       $Length = length $Encrypted;

       $Written_Size = $Written_Size + $Length;
       my $Rest = $Size - $Written_Size;
       print "$Size - $Written_Size = $Rest to go\n";
       }

   ### Cada 500 escrituras, comenzar a escribir en un archivo nuevo.
  if ($Count =~ /500$/)
    {
    close FILE;
    open(FILE,">>/tmp/WIPE_IT/$Count");
    }
  }

close FILE;
#----------------------------------------------------

my $Result = system ("umount $Partition");
if ($Result > 0) {print "Error desmontando partición $Partition, abortando.\n"; exit; }

  ### Reformateemos la partición. No borra datos, sólo los quita
  ### del directorio.
my $Result = system ("mkfs -t ext2 $Partition");
if ($Result > 0) {print "Error creando partición, abortando.\n"; exit;}

Conclusión

Usar Expect no era necesario (otros programas podrían haber resuelto los problemas simples que yo tenía). Usar Blowfish tampoco era necesario. De hecho, todo el script es muy largo si sólo quería borrar un disco rígido y llenarlo de espacios en blanco. Sin embargo yo quise usar fdisk porque siempre lo uso, y Expect es una herramienta tan poderosa que es bueno para la gente ver cómo funciona; agregar basura aleatoria binaria encriptada para confundir a un hacker es sólo un toque extra.

No comprendo la complejidad total de los discos rígidos, así que no estoy seguro de si quedan datos residuales en el disco rígido. Para mis propósitos, y mi nivel de seguridad, hace exactamente lo que necesito. Mientras más desarrollo MILAS, más seguro estoy de que habrá chequeos más exhaustivos y mejoras para borrar todos los datos de un disco rígido.

Tiendo a mirar hacia adelante en el tiempo tratando de anticipar cosas que puedan ser necesarias en el futuro, lo que siempre hace que un programador trabaje más de lo que requiere el proyecto en mano. Sin embargo, el buen ánimo me ganó y me gusta la dirección que está tomando el script, entonces, no me molesta escribr este artículo en un vuelo de avión. Hacer algo cool no me desgasta como tener que hacer un trabajo para otro, lo que es trabajo real.

Referencias

  1. Perl.com sitio web de Perl.

  2. Módulo Perl Expect

  3. Módulo Perl Blowfish

  4. Sitio original de este artículo. - http://www.gnujobs.com/Articles/14/Wipe_It.html (cualquier actualización será colocada aquí)

[También se puede usar /dev/random or /dev/urandom para sobreescribir un disco. Ver
The Answer Gang: "Classified Disk - Low-level Format" en número 60. Pero no realiza encripción. -Mike.]
Copyright © 2001, Mark Nielsen.
Copying license http://www.gacetadelinux.com/en/lg/copying.html
Published in Issue 63 of Linux Gazette, Mid-February (EXTRA) 2001