#!/usr/bin/perl -w # # flopcop - analyze a FAT12 floppy disk image # created with dd. Prints relevant information # such as the BIOS Parameter Block, root directory # entries, offsets and sizes of all files in the # root directory, and FAT chain image file offsets # for each file. # # limitations: does not traverse sub directories, # only analyzes root dir entries. # # usage: flopcop imagefile # # N. DeBaggis 10-9-2002 # use strict; my $dir_struct_len = 32; my $buf; my %bsect; my %geometry; my @files; open(FH, "$ARGV[0]"); binmode(FH); readboot(); printboot(); calcvalues(); print "$ARGV[0] Image file offsets:\n"; print '-' x 50 . "\n"; foreach(sort keys %geometry){ printf("%-20s = 0x%08x %d\n", $_, $geometry{$_}, $geometry{$_}); } my $rootbase = $geometry{'root_offset'}; for(my $i = 0; $i < $bsect{'root_entries'}; $i++){ push @files, readdirent($rootbase); $rootbase += $dir_struct_len; } print "\n$ARGV[0] MSDOS 8.3 filename info:\n"; foreach(@files){ $buf = (); if(($_->{'fileattr'} != 0x0f) && ($_->{'filecluster'} > 1)){ printdirent($_); print "\n"; } } close(FH); # # readboot() # # reads the first 512 bytes from the dd created image # stores the BIOS Parameter Block values in the %bsect # hash. # sub readboot{ seek(FH, 0, 0); read(FH, $buf, 512); $bsect{'jmp_opcode'} = unpack('@0H6',$buf); $bsect{'oem_name'} = unpack('@3a8', $buf); $bsect{'bytes_per_sector'} = unpack('@11S1', $buf); $bsect{'sectors_per_cluster'} = unpack('@13C1', $buf); $bsect{'reserved_sectors'} = unpack('@14S1', $buf); $bsect{'n_fats'} = unpack('@16C1', $buf); $bsect{'root_entries'} = unpack('@17S1', $buf); $bsect{'total_sectors'} = unpack('@19S1', $buf); $bsect{'media_descriptor'} = unpack('@21C1', $buf); $bsect{'sectors_per_fat'} = unpack('@22S1', $buf); $bsect{'sectors_per_track'} = unpack('@24S1', $buf); $bsect{'n_heads'} = unpack('@26S1', $buf); $bsect{'hidden_sectors'} = unpack('@28L1', $buf); $bsect{'total_sectors_big'} = unpack('@32L1', $buf); $bsect{'drive_number'} = unpack('@36C1', $buf); $bsect{'unused_byte'} = unpack('@37C1', $buf); $bsect{'ext_boot_signature'} = unpack('@38C1', $buf); $bsect{'serial_number'} = unpack('@39L1', $buf); $bsect{'volume_label'} = unpack('@43a11', $buf); $bsect{'fat_type'} = unpack('@54a8', $buf); $bsect{'boot_code'} = substr($buf, 62, 448); $bsect{'boot_signature'} = unpack('@510S1', $buf); } sub printboot{ print "$ARGV[0] BIOS Parameter Block:\n"; print '-' x 50 . "\n"; printf("%-20s = 0x%s\n", "jmp_opcode", $bsect{'jmp_opcode'}); printf("%-20s = %s\n", "oem_name", $bsect{'oem_name'}); printf("%-20s = %d\n", "bytes_per_sector", $bsect{'bytes_per_sector'}); printf("%-20s = %d\n", "sectors_per_cluster", $bsect{'sectors_per_cluster'}); printf("%-20s = %d\n", "reserved_sectors", $bsect{'reserved_sectors'}); printf("%-20s = %d\n", "n_fats", $bsect{'n_fats'}); printf("%-20s = %d\n", "root_entries", $bsect{'root_entries'}); printf("%-20s = %d\n", "total_sectors", $bsect{'total_sectors'}); printf("%-20s = 0x%02x\n", "media_descriptor", $bsect{'media_descriptor'}); printf("%-20s = %d\n", "sectors_per_fat", $bsect{'sectors_per_fat'}); printf("%-20s = %d\n", "sectors_per_track", $bsect{'sectors_per_track'}); printf("%-20s = %d\n", "n_heads", $bsect{'n_heads'}); printf("%-20s = %d\n", "hidden_sectors", $bsect{'hidden_sectors'}); printf("%-20s = %d\n", "total_sectors_big", $bsect{'total_sectors_big'}); printf("%-20s = %d\n", "drive_number", $bsect{'drive_number'}); printf("%-20s = 0x%02x\n", "unused_byte", $bsect{'unused_byte'}); printf("%-20s = 0x%02x\n", "ext_boot_signature", $bsect{'ext_boot_signature'}); printf("%-20s = 0x%04x\n", "serial_number", $bsect{'serial_number'}); printf("%-20s = %s\n", "volume_label", $bsect{'volume_label'}); printf("%-20s = %s\n", "fat_type", $bsect{'fat_type'}); printf("%-20s = 0x%04x\n", "boot_signature", $bsect{'boot_signature'}); print "\n"; print "boot_code = \n"; print '-' x 50 . "\n"; # from perldoc.com # http://www.perldoc.com/perl5.8.0/pod/perlpacktut.html my $i; print map { ++$i % 16 ? "$_ " : "$_\n" } unpack( 'H2' x length( $bsect{'boot_code'} ), $bsect{'boot_code'} ), length( $bsect{'boot_code'} ) % 16 ? "\n" : ''; print "\n"; } # # calcvalues() # # calculate the image file offsets and values for the key # locations of the disk image. # sub calcvalues{ $geometry{'fat_size'} = $bsect{'sectors_per_fat'} * $bsect{'bytes_per_sector'}; $geometry{'root_size'} = $bsect{'root_entries'} * $dir_struct_len; $geometry{'fat1_offset'} = $bsect{'bytes_per_sector'}; $geometry{'root_offset'} = $geometry{'fat_size'} * $bsect{'n_fats'} + $geometry{'fat1_offset'}; $geometry{'data_offset'} = $geometry{'root_size'} + $geometry{'root_offset'}; } # # getoffset($cluster) # # returns the dd created image file offset of # the FAT cluster. # sub getoffset{ my $cluster = shift; my $result = 0; $result = $cluster; $result -= 2; $result *= $bsect{'sectors_per_cluster'}; $result += $geometry{'data_offset'} / $bsect{'bytes_per_sector'}; $result *= $bsect{'bytes_per_sector'}; return $result; } # # followchain($cluster) # # follows the FAT chain from cluster # # returns a reference to the FAT chain array. # sub followchain{ my $cluster = shift; my $tmp; my @chain; my $foffset; push @chain, $cluster; $foffset = $cluster + ($cluster / 2); $tmp = $cluster; seek(FH, $geometry{'fat1_offset'} + $foffset, 0); read(FH, $cluster, 2); $cluster = unpack('S1', $cluster); if($tmp & 1){ $cluster >>= 4; } else{ $cluster &= 0x0fff; } while($cluster > 0x0 && $cluster < 0xff1){ push @chain, $cluster; $foffset = $cluster + ($cluster / 2); $tmp = $cluster; seek(FH, $geometry{'fat1_offset'} + $foffset, 0); read(FH, $cluster, 2); $cluster = unpack('S1', $cluster); if($tmp & 1){ $cluster >>= 4; } else{ $cluster &= 0x0fff; } } return \@chain; } # # readdirent($offset) # # reads an MSDOS 8.3 directory entry from the root directory # and stores the directory info into a hash. # # returns a reference to the dir entry hash. # sub readdirent{ my $offset = shift; seek(FH, $offset, 0); my $tmp; read(FH, $tmp, $dir_struct_len); my %dirstruct; $dirstruct{'filename'} = unpack('@0a8', $tmp); $dirstruct{'fileext'} = unpack('@8a3', $tmp); $dirstruct{'fileattr'} = unpack('@11C1', $tmp); $dirstruct{'filereserved'} = unpack('@12C9', $tmp); $dirstruct{'filetime'} = unpack('@22S1', $tmp); $dirstruct{'filedate'} = unpack('@24S1', $tmp); $dirstruct{'filecluster'} = unpack('@26S1', $tmp); $dirstruct{'filelength'} = unpack('@28L1', $tmp); $dirstruct{'dirblock'} = ($offset - $geometry{'root_offset'}) / 32; $dirstruct{'fileslack'} = 0; if(my $tmp = $dirstruct{'filelength'} % ($bsect{'bytes_per_sector'} * $bsect{'sectors_per_cluster'})){ $dirstruct{'fileslack'} = $bsect{'bytes_per_sector'} * $bsect{'sectors_per_cluster'} - $tmp; } return \%dirstruct; } # # print a directory entry from a dir hash. # sub printdirent{ my $hash = shift; my $chain; print '-' x 50 . "\n"; printf("%-20s = %s", "filename", $hash->{'filename'}); if($hash->{'filename'} =~ m/^\xe5/){ print " [deleted]\n"; } else{ print "\n"; } printf("%-20s = %s\n", "file extention", $hash->{'fileext'}); print "\n"; printf("%-20s - %s\n", "file attributes", "uuadvshr"); printf("%-20s = %s\n", "", unpack('B8', pack('C1', $hash->{'fileattr'}))); print "\n"; printf("%-20s = 0x%04x %s\n", "first cluster", $hash->{'filecluster'}, $hash->{'filecluster'}); printf("%-20s = 0x%08x %s\n", "image offset", getoffset($hash->{'filecluster'}), getoffset($hash->{'filecluster'})); printf("%-20s = 0x%08x %s\n", "file length", $hash->{'filelength'}, $hash->{'filelength'}); printf("%-20s = 0x%08x %s\n", "file slack", $hash->{'fileslack'}, $hash->{'fileslack'}); print "\n"; print "file cluster trace with image offset in hex\n[cluster:offset]:\n"; $chain = followchain($hash->{'filecluster'}); for(my $i = 0; $i < @$chain; $i++){ printf("%04x:%08x", @$chain[$i], getoffset(@$chain[$i])); if(($i + 1) % 4){ print " "; } else{ print "\n"; } } print "\n"; }