Vacunar disco (FAT32) contra un "AUTORUN.INF" malicioso

Últimamente se ha puesto de moda entre el "malware" utilizar discos extraíbles (memorias usb, cámaras de fotos, reproductores de mp3, etc ...) para propagarse de un ordenador a otro. Para conseguirlo crean en el directorio raíz del disco un archivo "AUTORUN.INF" que permite ejecutar el software malicioso cada vez que el disco es conectado a un ordenador. La pequeña aplicación que muestro a continuación permite "vacunar" este archivo de tal forma que resulte mucho más difícil (aunque no imposible) modificarlo, impidiendo de este modo que nuestro disco sea infectado cuando lo usemos en otros ordenadores.

ANTES DE CONTINUAR, RECUERDA QUE ESTA APLICACIÓN ES SOLO UN EJEMPLO. NO DEBE UTILIZARSE EN DISCOS QUE CONTENGAN DATOS IMPORTANTES, Y EN NINGÚN CASO ME HARÉ RESPONSABLE DE LOS POSIBLES DAÑOS QUE PUEDA SUFRIR EL DISCO O LOS DATOS CONTENIDOS EN EL.

USA ESTE PROGRAMA CON PRECAUCIÓN Y BAJO TU PROPIA RESPONSABILIDAD.

Para vacunar nuestro disco lo primero que tenemos que hacer es crear en el directorio raíz del disco un fichero llamado "AUTORUN.INF" (el archivo puede estar vacío)

Luego ejecutamos la aplicación como administradores, asegurándonos antes de que ningún programa esta usando el disco.

Despues de aceptar la clausula de exclusión de responsabilidad, se nos mostrará una pantalla como esta:

[0] Salir
[1] \\.\C:
[2] \\.\D:
[3] \\.\E:
[4] \\.\F:
[5] \\.\G:
Escoge:

Escogemos el número que corresponde a la unidad que queremos vacunar y pulsamos [Enter].

Se mostrará entonces una serie de información referente al disco, y si todo va bien, al final aparecerá el siguiente mensaje:

AUTORUN.INF encontrado
Activando vacuna ...
Proceso terminado

Ahora el fichero "AUTORUN.INF" que habíamos creado previamente, aparece oculto y "marcado" de tal manera que es inaccesible para cualquier programa.

No lo podremos ni abrir, ni modificar, ni siquiera borrarlo impidiendo de esta manera que ningún software malicioso lo reemplace por uno infectado. La única manera de eliminarlo es quitando la vacuna con este mismo programa, o formateando el disco. Aunque como ya dije mas arriba el método no es infalible, los fabricantes de malware también conocen esta técnica y podrían desmarcar el fichero para luego eliminarlo, pero la verdad es que la mayoría no se molestan en hacerlo, ademas como se necesitan permisos de administrador llamarían mucho la atención. La realidad es que algunas marcas de antivirus conocidas que también "vacunan" discos extraibles usan esta técnica, y si a ellos les parece segura, a mi también.

Para quitar la vacuna solo hay que volver a repetir los pasos anteriores, pero en este caso, al estar el disco ya vacunado, se mostrará el siguiente mensaje:

AUTORUN.INF encontrado
Eliminando vacuna ...
Proceso terminado

El código fuente y el programa ya compilado se pueden bajar de aquí

*** DESCRIPCIÓN DEL CÓDIGO FUENTE DEL PROGRAMA (SOLO PARA PROGRAMADORES) ***

El código fuente es el siguiente (es una aplicación de consola)

unit umain;
 
interface
 
uses Windows, Sysutils, Classes;
 
procedure main;
 
implementation
 
type
  PDWordArray = ^TDWordArray;
  TDWordArray = array[0..$0FFFFFFF] of DWord;
 
  TPartition = record
    Stream: TFileStream;
    PartBegin: DWord;
    BytsPerSec: Word;
    SecPerClus: Byte;
    BytsPerClus: Word;
    RsvdSecCnt: Word;
    NumFATs: Byte;
    FATSz32: Word;
    ClusCount: DWord;
    ClusBegin: DWord;
    RootClus: DWord;
    FAT: PDWordArray;
  end; 
 
function Min(i,j: Integer): Integer;
begin
  if i < j then
    Result:= i
  else
    Result:= j;
end;
 
// Muestra el contenido del buffer en hexadecimal y como texto
procedure WriteHex(Buffer: PAnsiChar;  Count: Integer);
var
  i,j: Integer;
begin
  j:= 0;
  while Count > 0 do
  begin
    Write(IntToHex(j,8) + ':' + #32#32);
    for i:= 0 to Min(Count,8) - 1 do
      Write(IntToHex(Byte(Buffer[i]),2) + #32);
    Write(#32);
    for i:= 8 to Min(Count,16) - 1 do
      Write(IntToHex(Byte(Buffer[i]),2) + #32);
    for i:= Min(Count,16) to 15 do
      Write(#32#32#32);
    Write(#32 + '|');
    for i:= 0 to Min(Count,16) - 1 do
    if Char(Buffer[i]) in ['A'..'Z','a'..'z','0'..'9'] then
      Write(Buffer[i])
    else
      Write('.');
    Writeln('|');
    Dec(Count,16);
    inc(Buffer,16);
    inc(j,16);
  end;
end;
 
// Lee el primer sector de la particion
procedure ReadBootSector(var Partition: TPartition);
var
  Buffer: PByteArray;
begin
  // Reservamos memoria para guardar el sector de arranque
  GetMem(Buffer,$0200);
  try
    with Partition do
    begin
      // Leemos el sector de arranque
      Stream.ReadBuffer(Buffer^,$0200);
      // Volcamos en pantalla el contenido del sector de arranque
      Writeln('=== Boot Sector ===');
      WriteHex(PAnsiChar(Buffer), $0200);
      Writeln;
      // Comprobamos la firma del sector de arranque
      if PWORD(@Buffer[$01FE])^ <> $AA55 then
        raise Exception.Create('Invalid Boot Sector signature = '
          + IntToHex(Swap(PWORD(@Buffer[$01FE])^),2));
      // Comprobamos que es FAT32
      if Trim(Copy(PAnsiChar(@Buffer[$0052]),1,8)) <> 'FAT32' then
        raise Exception.Create('Invalid System ID = '
          + Trim(Copy(AnsiString(PAnsiChar(@Buffer[$0052])),1,8)));
      // Mostramos la informacion de la particion por pantalla
      Writeln('Signature:           ' + IntToHex(Swap(PWORD(@Buffer[$01FE])^),2));
      Writeln('System ID:           ' + Trim(Copy(PAnsiChar(@Buffer[$0052]),1,8)));
      Writeln('OEM ID:              ' + Trim(Copy(PAnsiChar(@Buffer[$0003]),1,8)));
      Writeln('Volume label:        ' + Trim(Copy(PAnsiChar(@Buffer[$0047]),1,11)));
      Writeln('Volume serial:       ' + IntToHex(PDWORD(@Buffer[$0043])^,8));
      BytsPerSec:= PWORD(@Buffer[$000B])^;
      Writeln('Bytes per sector:    ' + IntToStr(BytsPerSec));
      SecPerClus:= PByte(@Buffer[$000D])^;
      Writeln('Sectors per cluster: ' + IntToStr(SecPerClus));
      BytsPerClus:= BytsPerSec * SecPerClus;
      Writeln('Bytes per cluster:   ' + IntToStr(BytsPerClus));
      RsvdSecCnt:= PWORD(@Buffer[$000E])^;
      Writeln('Reserved sectors:    ' + IntToStr(RsvdSecCnt));
      NumFATs:= PByte(@Buffer[$0010])^;
      Writeln('Number of FATs:      ' + IntToStr(NumFATs));
      FATSz32:= PWORD(@Buffer[$0024])^;
      Writeln('Sectors per FAT:     ' + IntToStr(FATSz32));
      ClusCount:= ((FATSz32 * BytsPerSec) div SizeOf(DWord));
      Writeln('Cluster count:       ' + IntToStr(ClusCount));
      ClusBegin:= RsvdSecCnt + (NumFATs * FATSz32);
      RootClus:= PDWORD(@Buffer[$002C])^;
      Writeln('Root cluster:        ' + IntToStr(RootClus));
      Writeln;
    end;
  finally
    // Liberamos la memoria
    FreeMem(Buffer);
  end;
end;
 
// Lee la FAT
procedure ReadFAT(Partition: TPartition);
var
  i: DWord;
  FreeClus: int64;
  ResClus: int64;
  BadClus: int64;
begin
  FreeClus:= 0;
  ResClus:= 0;
  BadClus:= 0;
  with Partition do
  begin
    // Nos colocamos al principio de la FAT
    Stream.Position:= (PartBegin + RsvdSecCnt) * BytsPerSec;
    // La cargamos en memoria
    Stream.ReadBuffer(FAT^, FATSz32 * BytsPerSec);
    Writeln('=== Clusters ===');
    // La recorremos registro a registro
    for i:= 0 to ClusCount - 1 do
    begin
      // y contamos el estado de cada cluster
      case FAT[i] of
        0: inc(FreeClus);
        $FFFFFF7: inc(BadClus);
      else
        inc(ResClus);
      end;
    end;
    // Mostramos los contadores
    Writeln('Free: ' + IntToStr(FreeClus));
    Writeln('Used: ' + IntToStr(ResClus));
    Writeln('Bad:  ' + IntToStr(BadClus));
    Writeln;
  end;
end;
 
// Lee un cluster
procedure ReadClus(Partition: TPartition; Cluster: DWord; Buffer: PByte);
begin
  with Partition do
  begin
    Stream.Position:= (PartBegin + ClusBegin + (Cluster - 2) * SecPerClus)
      * BytsPerSec;
    Stream.ReadBuffer(Buffer^, BytsPerClus);
  end;
end;
 
// Escribe un cluster
procedure WriteClus(Partition: TPartition; Cluster: DWord; Buffer: PByte);
begin
  with Partition do
  begin
    Stream.Position:= (PartBegin + ClusBegin + (Cluster - 2) * SecPerClus)
      * BytsPerSec;
    Stream.WriteBuffer(Buffer^, BytsPerClus);
  end;
end;
 
// Lee los datos del directorio a partir de su primer cluster
procedure ReadData(Partition: TPartition; Cluster: DWord; Stream: TStream);
var
  Buffer: PByte;
begin
  // Reservamos memoria para guardar los datos de un cluster
  GetMem(Buffer,Partition.SecPerClus * Partition.BytsPerSec);
  try
    // Mientras no se llegue al final del fichero
    while Cluster < Partition.ClusCount do
    begin
      // Leemos el cluster
      ReadClus(Partition, Cluster, Buffer);
      // Lo grabamos en el stream
      Stream.WriteBuffer(Buffer^, Partition.SecPerClus * Partition.BytsPerSec);
      // Buscamos en la FAT el siguiente cluster
      Cluster:= Partition.FAT[Cluster];
    end;
  finally
    FreeMem(Buffer);
  end;
end;
 
// Escribe los datos del directorio a partir de su primer cluster
procedure WriteData(Partition: TPartition; Cluster: DWord; Stream: TStream);
var
  Buffer: PByte;
begin
  // Reservamos memoria para guardar los datos de un cluster
  GetMem(Buffer,Partition.SecPerClus * Partition.BytsPerSec);
  try
    // Mientras no se llegue al final del fichero
    while Cluster < Partition.ClusCount do
    begin
      // Leemos el cluster del stream
      Stream.ReadBuffer(Buffer^, Partition.SecPerClus * Partition.BytsPerSec);
      // Escribimos el cluster en el disco
      WriteClus(Partition, Cluster, Buffer);
      // Buscamos en la FAT el siguiente cluster
      Cluster:= Partition.FAT[Cluster];
    end;
  finally
    FreeMem(Buffer);
  end;
end;
 
// Vacuna el archivo "AUTORUN.INF"
function Vacunar(Partition: TPartition; Cluster: DWord): Boolean;
var
  Tmp: TMemoryStream;
  Entry: PAnsiChar;
  Name: String;
  Attr: Byte;
begin
  Result:= FALSE;
  // Creamos un stream temporal
  Tmp:= TMemoryStream.Create;
  // Reservamos memoria para guardar cada entrada del directorio
  GetMem(Entry, 32);
  try
    // Leemos todas las entradas del directorio
    ReadData(Partition,Cluster,Tmp);
    Tmp.Position:= 0;
    // Procesamos cada una de las entradas del directorio
    while Tmp.Read(Entry^, 32) = 32 do
    begin
      // Si el primer byte es cero, significa que es la ultima
      if Entry[0] = #0 then
        break;
      // Si el primer bytes es alguno de estos, ignoramos la entrada
      if Entry[0] in [#$00,#$05,#$2E,#$E5] then
        continue;
      Attr:= Byte(Entry[$0B]);
      // Ignoramos los nombres largos
      if (Attr and $0F) = $0F then
        continue;
      // Obtenemos la extension
      Name:= Trim(Copy(Entry,9,3));
      if Name <> EmptyStr then
        Name:= '.' + Name;
      // y se la sumamos al nombre
      Name:= Trim(Copy(Entry,1,8)) + Name;
      // Si encontramos el archivo "AUTORUN.INF" lo vacunamos
      if Uppercase(Name) = 'AUTORUN.INF' then
      begin
        Result:= TRUE;
        Writeln('AUTORUN.INF encontrado');
        // Si no esta vacunado
        if Entry[$0B] <> #$42 then
        begin
          // Lo vacunamos
          Writeln('Activando vacuna ...');
          Entry[$0B]:= #$42;
        end else
        begin
          // y si lo esta eliminamos la vacuna
          Writeln('Eliminando vacuna ...');
          Entry[$0B]:= #0;
        end;
        // Cambiamos la entrada
        Tmp.Position:= Tmp.Position - 32;
        Tmp.WriteBuffer(Entry^,32);
        Tmp.Position:= 0;
        // Y volvemos a escribir los datos en el disco
        WriteData(Partition,Cluster,Tmp);
        Writeln('Proceso terminado');
        break;
      end;
    end;
  finally
    // Liberamos la memoria y el stream
    FreeMem(Entry);
    Tmp.Free;
  end;
end;
 
// Esta es la funcion principal
procedure main;
var
  C: Char;
  i, H: Integer;
  Str: String;
  Stream: TFileStream;
  Partition: TPartition;
begin
  Writeln;
  Writeln('Creado por Domingo Seoane - <a href="http://delphi.jmrds.com'">http://delphi.jmrds.com'</a>);
  Writeln;
  Writeln('ANTES DE CONTINUAR, RECUERDA QUE ESTE PROGRAMA ES SOLO UN EJEMPLO:');
  Writeln;
  Writeln('--> NO DEBE UTILIZARSE EN DISCOS QUE CONTENGAN DATOS IMPORTANTES.');
  Writeln;
  Writeln('--> EN NINGUN CASO ME HARE RESPONSABLE DE LOS DA' + #165 + 'OS QUE PUEDA SUFRIR EL DISCO.');
  Writeln;
  Writeln('--> TAMPOCO DE LOS DA' + #165 + 'OS QUE PUEDAN SUFRIR LOS DATOS CONTENIDOS EN EL.');
  Writeln;
  Writeln('--> USA ESTE PROGRAMA CON PRECAUCION Y BAJO TU PROPIA RESPONSABILIDAD.');
  Writeln;
  while TRUE  do
  begin
    Write('ERES CONSCIENTE DEL RIESGO Y QUIERES CONTINUAR? (S/N)');
    Readln(Str);
    if Uppercase(Str) = 'S' then
      break;
    if Uppercase(Str) = 'N' then
      halt;
  end;
  Writeln;
  try
    // Creamos la lista de opciones
    with TStringList.Create do
    try
      Add(EmptyStr);
      Writeln('[' + IntToStr(Count-1) + '] Salir');
      // Buscamos todas las unidades
      for C:= 'A' to 'Z' do
      begin
        Str:= '\\.\' + C + ':';
        H:= FileOpen(PAnsiChar(Str),fmOpenRead or fmShareDenyNone);
        // Si se puede abrir lo añadimos a la lista
        if H > 0 then
        begin
          CloseHandle(H);
          Add(Str);
          Writeln('[' + IntToStr(Count-1) + '] ' + Str);
        end;
      end;
      Writeln;
      i:= -1;
      while i < 0  do
      begin
        Write('Escoge: ');
        Readln(Str);
        i:= StrToIntDef(Str,-1);
      end;
      Writeln;
      if i > 0 then
        Stream:= TFileStream.Create(Strings[i],fmOpenReadWrite or fmShareExclusive)
      else
        Exit;
    finally
      Free;
    end;
    if Stream = nil then
      Halt;
    try
      // Borramos todas las variables
      Fillchar(Partition,Sizeof(Partition),#0);
      Partition.Stream:= Stream; 
      // Leemos el sector de arranque de la particion
      ReadBootSector(Partition);
      // Reservamos espacio en memoria para la FAT
      with Partition do
        GetMem(FAT,FATSz32 * BytsPerSec);
      try
        // Leemos la FAT y la guardamos en memoria
        ReadFAT(Partition);
        // Creamos o eliminamos la vacuna
        if not Vacunar(Partition, Partition.RootClus) then
          Writeln('No puedo encontrar el fichero "AUTORUN.INF"');
      finally
        // Liberamos la memoria reservada para la FAT
        FreeMem(Partition.FAT);
      end;
    finally
      // Liberamos el stream
      Stream.Free;
    end;
  except
    // Si ocurre un error
    On E: Exception do
    begin
      // Lo mostramos
      Writeln('Exception: ' + E.Message);
      Writeln('LastError: ' + SysErrorMessage(GetLastError));
      Writeln('ParamStr(1): ' + ParamStr(1));
    end;
  end;
  Writeln;
  Writeln('Pulsa [ENTER] para cerrar el programa ...');
  Readln;
end;
 
end.

En el código se puede ver como el programa accede directamente a los datos de la partición, busca el fichero "autorun.inf" y lo marca con un flag especial que lo vuelve "intocable".

Para saber mas revisa estos enlaces con documentación sobre la FAT32:
http://www.pjrc.com/tech/8051/ide/fat32.html
http://en.wikipedia.org/wiki/File_Allocation_Table
http://en.wikipedia.org/wiki/Master_boot_record
http://technet.microsoft.com/en-us/library/cc977221.aspx
http://delphi.jmrds.com/node/74

Comentarios

Estimado Seoane:
Existe un truco más simple y yo diría que más "perfeccionado".
El truco no es nada más que crear una simple carpeta llamada autorun.inf en el raiz.
No hace falta ocultarla ni cambiarle los permisos, aunque también se puede hacer.
No se puede crear o sobreescribir un archivo autorun.inf porque la carpeta lo evita, y borrar una carpeta (para un programa/virus) es más "complejo" que un archivo por lo que no lo suelen poder hacer.
A mi me ha funcionado muy bien durante años.
Un saludo.

Borrar una carpeta no es nada complicado, es algo trivial, aunque no dudo de que habrá muchos virus que ni se molesten. Todo depende de lo difícil que se lo quieras poner, si a ti te llega con crear una carpeta estupendo.

Saludos