Leer contenido de la FAT

El siguiente programa permite leer el sector de arranque de una partición del tipo FAT32, el mismo tipo de partición que usan la mayoría de las memorias USB y tarjetas de memoria, y volcar la información que contiene, así como extraer la estructura de directorios directamente de la FAT. La lectura se puede hacer directamente sobre un disco, o sobre un fichero de imagen creado con alguna herramienta como Dump. En futuras versiones tengo intención de poder extraer el contenido de los ficheros, aunque por ahora solo podemos ver su nombre.

El código 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;
    PartType: Byte;
    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;
 
function FormatFileSize(const Bytes: int64): String;
const
  B = 1;          // Byte
  KB = 1024 * B;  // Kilobyte
  MB = 1024 * KB; // Megabyte
  GB = 1024 * MB; // Gigabyte
begin
  if Bytes > GB then
    Result := FormatFloat('0.00 GB', Bytes / GB)
  else if Bytes > MB then
    Result := FormatFloat('0.00 MB', Bytes / MB)
  else if Bytes > KB then
    Result := FormatFloat('0.00 KB', Bytes / KB)
  else
    Result := FormatFloat(   '0   ', Bytes);
end;
 
// Lee el MasterBootRecord
procedure ReadMasterBootRecord(var Partition: TPartition);
var
  i,j: Integer;
  Buffer: PByteArray;
begin
  // Reservamos memoria para guardar el MBR
  GetMem(Buffer,$0200);
  try
    with Partition do
    begin
      // Leemos el MBR
      Stream.ReadBuffer(Buffer^,$0200);
      // Volcamos en pantalla el contenido del MBR
      Writeln('=== Master Boot Record ===');
      WriteHex(PAnsiChar(Buffer), $0200);
      Writeln;
      // Comprobamos la firma del MBR
      if PWORD(@Buffer[$01FE])^ <> $AA55 then
        raise Exception.Create('Invalid MBR signature = '
          + IntToHex(Swap(PWORD(@Buffer[$01FE])^),2));
      // Volcamos la tabla de particiones
      Writeln('=== Table ===');
      WriteHex(PAnsiChar(@Buffer[$01BE]), $0040);
      Writeln;
      // Volcamos cada entrada de la tabla de particiones
      Writeln('=== Partitions ===');
      for i:= 0 to 3 do
      begin
        j:= $01BE + (16 * i);
        if Buffer[j] = $80 then
          Writeln(Format('[%d] %8.8x/%8.8x A',[i,
            PDWORD(@Buffer[j + $08])^,PDWORD(@Buffer[j + $0C])^]))
        else
          Writeln(Format('[%d] %8.8x/%8.8x',[i,
            PDWORD(@Buffer[j + $08])^,PDWORD(@Buffer[j + $0C])^]))
      end;
      Writeln;
      // Usamos siempre la primera particion
      Writeln('Go to partition 0 ...');
      Writeln;
      PartType:= Buffer[$01BE + $04];
      PartBegin:= PDWORD(@Buffer[$01BE + $08])^;
    end;
  finally
    FreeMem(Buffer);
  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 PartType <> 0 then
      begin
        // si no lo es mostramos el error
        if not (PartType in [$0B,$0C]) then
          raise Exception.Create('Invalid Partition Type = 0x'
            + IntToHex(PartType,2));
        Writeln('Partiton type:       0x' + IntToHex(PartType,2) + ' (FAT32)');
      end else
      begin
        // Si no queda mas remedio usamos el "System ID"
        if Trim(Copy(PAnsiChar(@Buffer[$0052]),1,8)) <> 'FAT32' then
          raise Exception.Create('Invalid System ID = '
            + Trim(Copy(AnsiString(PAnsiChar(@Buffer[$0052])),1,8)));
      end;
      // 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
    // No 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;
 
// Escribe la FAT en el disco
procedure WriteFAT(Partition: TPartition);
var
  i: Byte;
begin
  with Partition do
  begin
    Stream.Position:= (PartBegin + RsvdSecCnt) * BytsPerSec;
    // La escribimos tantas veces como indique el parametro NumFATs
    for i:= 1 to NumFATs do
      Stream.WriteBuffer(FAT^, FATSz32 * BytsPerSec);
  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 de un fichero a partir de su primer cluster
procedure ReadFileData(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;
 
// Muestra la estructura de un directorio
procedure Tree(Partition: TPartition; Cluster: DWord; Path: String);
var
  Tmp: TMemoryStream;
  Entry: PAnsiChar;
  Name: String;
  Attr: Byte;
  Size: DWord;
  FTime: TFILETIME;
  STime: TSYSTEMTIME;
  FTimeStr: String;
begin
  // 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
    ReadFileData(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;
      // Obtenemos el tamaño
      Size:= PDWord(@Entry[$1C])^;
      // La fecha de modificación
      DosDateTimeToFileTime(PDWord(@Entry[$18])^,PDWord(@Entry[$16])^,FTime);
      FileTimeToSystemTime(Ftime, STime);
      FTimeStr:= FormatDateTime('dd/mm/yyyy  hh:nn', SystemTimeToDateTime(STime));
      // Si es un directorio
      if Attr and faDirectory = faDirectory then
      begin
        Writeln(Format('%s  %10.10s  %s%s',[FTimeStr,'<DIR>',Path,Name]));
        // volvemos a llamar esta funcion de forma recursiva
        Tree(Partition, (PWord(@Entry[$14])^ * $10000) + PWord(@Entry[$1A])^,
          Path + Name + '\');
      end else
        Writeln(Format('%s  %10.10s  %s%s',[FTimeStr,FormatFileSize(Size),Path,Name]));
    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: Integer;
  H: Integer;
  Str: String;
  LeerMBR: Boolean;
  Stream: TFileStream;
  Partition: TPartition;
begin
  try
    // Si el archivo como parametro
    if (ParamCount > 0) and (Copy(ParamStr(1),1,1) <> '/') then
    begin
      LeerMBR:= FindCmdLineSwitch('MBR',TRUE);
      Stream:= TFileStream.Create(ParamStr(1),fmOpenRead or fmShareDenyWrite);
    end else
    begin
      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);
            AddObject(Str,TObject(0));
            Writeln('[' + IntToStr(Count-1) + '] ' + Str);
          end;
        end;
        // Buscamos todos los discos (al menos los 16 primeros)
        for i:= 0 to 16 do
        begin
          Str:= '\\.\PhysicalDrive' + IntTostr(i);
          H:= FileOpen(PAnsiChar(Str),fmOpenRead or fmShareDenyNone);
          // Si se puede abrir lo añadimos a la lista
          if H > 0 then
          begin
            CloseHandle(H);
            AddObject(Str,TObject(1));
            Writeln('[' + IntToStr(Count-1) + '] ' + Str);
          end;
        end;
        i:= -1;
        while i < 0  do
        begin
          Write('Escoge: ');
          Readln(Str);
          i:= StrToIntDef(Str,-1);
        end;
        Writeln;
        if i > 0 then
        begin
          Stream:= TFileStream.Create(Strings[i],fmOpenRead or fmShareDenyWrite);
          LeerMBR:= Integer(Objects[i]) = 1;
        end else
          Exit;
      finally
        Free;
      end;
    end;
    if Stream = nil then
      Halt;
    try
      // Borramos todas las variables
      Fillchar(Partition,Sizeof(Partition),#0);
      Partition.Stream:= Stream;
      // Si nos pasan el parametro /MBR
      if LeerMBR then
      begin
        // Leemos el MBR y buscamos la primera particion
        ReadMasterBootRecord(Partition);
        Stream.Position:= 512 * Partition.PartBegin;
      end;
      // 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);
        // Volcamos la informacion en pantalla
        Tree(Partition, Partition.RootClus, '\');
      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.

El funcionamiento es muy simple, solo hay que tener la precaución de ejecutarlo como administrador

Si no se pasa ningún fichero como parámetro se dan a escoger entre los discos del sistema:

C:\fatexp>fatexp
[0] Salir
[1] \\.\C:
[2] \\.\D:
[3] \\.\E:
[4] \\.\F:
[5] \\.\PhysicalDrive0
[6] \\.\PhysicalDrive1
[7] \\.\PhysicalDrive2
Escoge:

Solo tenemos que escoger el disco y nos mostrara algo como esto:

[0] Salir
[1] \\.\C:
[2] \\.\D:
[3] \\.\E:
[4] \\.\F:
[5] \\.\G:
[6] \\.\PhysicalDrive0
[7] \\.\PhysicalDrive1
[8] \\.\PhysicalDrive2
[9] \\.\PhysicalDrive3
Escoge: 
=== Master Boot Record ===
00000000:  33 C0 8E D0 BC 00 7C 8E  C0 8E D8 BE 00 7C BF 00  |3...............|
00000010:  06 B9 00 02 FC F3 A4 50  68 1C 06 CB FB B9 04 00  |.......Ph.......|
00000020:  BD BE 07 80 7E 00 00 7C  0B 0F 85 0E 01 83 C5 10  |................|
00000030:  E2 F1 CD 18 88 56 00 55  C6 46 11 05 C6 46 10 00  |.....V.U.F...F..|
00000040:  B4 41 BB AA 55 CD 13 5D  72 0F 81 FB 55 AA 75 09  |.A..U...r...U.u.|
00000050:  F7 C1 01 00 74 03 FE 46  10 66 60 80 7E 10 00 74  |....t..F.f.....t|
00000060:  26 66 68 00 00 00 00 66  FF 76 08 68 00 00 68 00  |.fh....f.v.h..h.|
00000070:  7C 68 01 00 68 10 00 B4  42 8A 56 00 8B F4 CD 13  |.h..h...B.V.....|
00000080:  9F 83 C4 10 9E EB 14 B8  01 02 BB 00 7C 8A 56 00  |..............V.|
00000090:  8A 76 01 8A 4E 02 8A 6E  03 CD 13 66 61 73 1C FE  |.v..N..n...fas..|
000000A0:  4E 11 75 0C 80 7E 00 80  0F 84 8A 00 B2 80 EB 84  |N.u.............|
000000B0:  55 32 E4 8A 56 00 CD 13  5D EB 9E 81 3E FE 7D 55  |U2..V..........U|
000000C0:  AA 75 6E FF 76 00 E8 8D  00 75 17 FA B0 D1 E6 64  |.un.v....u.....d|
000000D0:  E8 83 00 B0 DF E6 60 E8  7C 00 B0 FF E6 64 E8 75  |.............d.u|
000000E0:  00 FB B8 00 BB CD 1A 66  23 C0 75 3B 66 81 FB 54  |.......f..u.f..T|
000000F0:  43 50 41 75 32 81 F9 02  01 72 2C 66 68 07 BB 00  |CPAu2....r.fh...|
00000100:  00 66 68 00 02 00 00 66  68 08 00 00 00 66 53 66  |.fh....fh....fSf|
00000110:  53 66 55 66 68 00 00 00  00 66 68 00 7C 00 00 66  |SfUfh....fh....f|
00000120:  61 68 00 00 07 CD 1A 5A  32 F6 EA 00 7C 00 00 CD  |ah.....Z2.......|
00000130:  18 A0 B7 07 EB 08 A0 B6  07 EB 03 A0 B5 07 32 E4  |..............2.|
00000140:  05 00 07 8B F0 AC 3C 00  74 09 BB 07 00 B4 0E CD  |........t.......|
00000150:  10 EB F2 F4 EB FD 2B C9  E4 64 EB 00 24 02 E0 F8  |.........d......|
00000160:  24 02 C3 49 6E 76 61 6C  69 64 20 70 61 72 74 69  |...Invalid.parti|
00000170:  74 69 6F 6E 20 74 61 62  6C 65 00 45 72 72 6F 72  |tion.table.Error|
00000180:  20 6C 6F 61 64 69 6E 67  20 6F 70 65 72 61 74 69  |.loading.operati|
00000190:  6E 67 20 73 79 73 74 65  6D 00 4D 69 73 73 69 6E  |ng.system.Missin|
000001A0:  67 20 6F 70 65 72 61 74  69 6E 67 20 73 79 73 74  |g.operating.syst|
000001B0:  65 6D 00 00 00 63 7B 9A  00 00 00 00 00 00 80 02  |em...c..........|
000001C0:  03 00 0B FE 7F EB 80 00  00 00 80 BF 78 00 00 00  |............x...|
000001D0:  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
000001E0:  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
000001F0:  00 00 00 00 00 00 00 00  00 00 00 00 00 00 55 AA  |..............U.|
 
=== Table ===
00000000:  80 02 03 00 0B FE 7F EB  80 00 00 00 80 BF 78 00  |..............x.|
00000010:  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000020:  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000030:  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
 
=== Partitions ===
[0] 00000080/0078BF80 A
[1] 00000000/00000000
[2] 00000000/00000000
[3] 00000000/00000000
 
Go to partition 0 ...
 
=== Boot Sector ===
00000000:  EB 58 90 4D 53 44 4F 53  35 2E 30 00 02 08 C0 03  |.X.MSDOS5.0.....|
00000010:  02 00 00 00 00 F8 00 00  3F 00 FF 00 80 00 00 00  |................|
00000020:  80 BF 78 00 20 1E 00 00  00 00 00 00 02 00 00 00  |..x.............|
00000030:  01 00 06 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000040:  80 00 29 47 5D 32 AA 4E  4F 20 4E 41 4D 45 20 20  |...G.2.NO.NAME..|
00000050:  20 20 46 41 54 33 32 20  20 20 33 C9 8E D1 BC F4  |..FAT32...3.....|
00000060:  7B 8E C1 8E D9 BD 00 7C  88 4E 02 8A 56 40 B4 41  |.........N..V..A|
00000070:  BB AA 55 CD 13 72 10 81  FB 55 AA 75 0A F6 C1 01  |..U..r...U.u....|
00000080:  74 05 FE 46 02 EB 2D 8A  56 40 B4 08 CD 13 73 05  |t..F....V.....s.|
00000090:  B9 FF FF 8A F1 66 0F B6  C6 40 66 0F B6 D1 80 E2  |.....f....f.....|
000000A0:  3F F7 E2 86 CD C0 ED 06  41 66 0F B7 C9 66 F7 E1  |........Af...f..|
000000B0:  66 89 46 F8 83 7E 16 00  75 38 83 7E 2A 00 77 32  |f.F.....u8....w2|
000000C0:  66 8B 46 1C 66 83 C0 0C  BB 00 80 B9 01 00 E8 2B  |f.F.f...........|
000000D0:  00 E9 2C 03 A0 FA 7D B4  7D 8B F0 AC 84 C0 74 17  |..............t.|
000000E0:  3C FF 74 09 B4 0E BB 07  00 CD 10 EB EE A0 FB 7D  |..t.............|
000000F0:  EB E5 A0 F9 7D EB E0 98  CD 16 CD 19 66 60 80 7E  |............f...|
00000100:  02 00 0F 84 20 00 66 6A  00 66 50 06 53 66 68 10  |......fj.fP.Sfh.|
00000110:  00 01 00 B4 42 8A 56 40  8B F4 CD 13 66 58 66 58  |....B.V.....fXfX|
00000120:  66 58 66 58 EB 33 66 3B  46 F8 72 03 F9 EB 2A 66  |fXfX.3f.F.r....f|
00000130:  33 D2 66 0F B7 4E 18 66  F7 F1 FE C2 8A CA 66 8B  |3.f..N.f......f.|
00000140:  D0 66 C1 EA 10 F7 76 1A  86 D6 8A 56 40 8A E8 C0  |.f....v....V....|
00000150:  E4 06 0A CC B8 01 02 CD  13 66 61 0F 82 75 FF 81  |.........fa..u..|
00000160:  C3 00 02 66 40 49 75 94  C3 42 4F 4F 54 4D 47 52  |...f.Iu..BOOTMGR|
00000170:  20 20 20 20 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000180:  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000190:  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
000001A0:  00 00 00 00 00 00 00 00  00 00 00 00 0D 0A 52 65  |..............Re|
000001B0:  6D 6F 76 65 20 64 69 73  6B 73 20 6F 72 20 6F 74  |move.disks.or.ot|
000001C0:  68 65 72 20 6D 65 64 69  61 2E FF 0D 0A 44 69 73  |her.media....Dis|
000001D0:  6B 20 65 72 72 6F 72 FF  0D 0A 50 72 65 73 73 20  |k.error...Press.|
000001E0:  61 6E 79 20 6B 65 79 20  74 6F 20 72 65 73 74 61  |any.key.to.resta|
000001F0:  72 74 0D 0A 00 00 00 00  00 AC CB D8 00 00 55 AA  |rt............U.|
 
Partiton type:       0x0B (FAT32)
Signature:           55AA
System ID:           FAT32
OEM ID:              MSDOS5.0
Volume label:        NO NAME
Volume serial:       AA325D47
Bytes per sector:    512
Sectors per cluster: 8
Bytes per cluster:   4096
Reserved sectors:    960
Number of FATs:      2
Sectors per FAT:     7712
Cluster count:       987136
Root cluster:        2
 
=== Clusters ===
Free: 983750
Used: 3386
Bad:  0
 
27/04/2012  22:42       <DIR>  \UNO
27/04/2012  22:42        0     \UNO\TRES.TXT
27/04/2012  22:42        0     \DOS.TXT
22/01/2012  17:47       <DIR>  \WILLIA~1
14/06/2011  18:39   625,31 KB  \WILLIA~1\LAMµQU~1.AZW
14/06/2011  18:39   466,13 KB  \WILLIA~1\MONALI~1.MOB
14/06/2011  18:39   341,71 KB  \WILLIA~1\NEUROM~1.AZW
22/01/2012  17:47       <DIR>  \WILLIA~2
14/06/2011  18:39   305,49 KB  \WILLIA~2\ELSE¥O~1.MOB
... etc ...

Por ultimo solo recordar que el programa se puede usar con imágenes de disco creadas con Dump, por ejemplo:

fatexp imagen.bin

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

Enlaces de interés:
Dump - http://delphi.jmrds.com/node/45
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