FAT32 Analysis Script
Not completed, but gives some useful information before entering an infinite loop following FAT Chains. :-P
1 #!/usr/bin/perl
2 #
3 #
4 # FAT32 Analysis Script by Mark Duller (May 9, 2008)
5 #
6 # References:
7 #
8 # http://perldoc.perl.org/
9 # http://en.wikipedia.org/wiki/Master_boot_record
10 # http://en.wikipedia.org/wiki/File_Allocation_Table
11 use strict;
12 use warnings;
13
14
15 print "FAT32 Analysis Script by Mark Duller (May 9, 2008)\n\n";
16
17
18 # Turn of output buffer
19 $|++;
20
21 #$infile = $ARGV[0];
22 #$infile = "/usr/local/ad6_10_8.5gb";
23 #$infile = "ad6_4_0gb";
24 my $infile = "/dev/da0s2";
25
26 print "Reading from \"$infile\"\n\n";
27 my $insize = -s $infile;
28 # Open disk
29 open(IN, "<", $infile) or die $!;
30 binmode IN;
31
32
33 ###################################################
34 # check first byte of partition record
35 seek(IN, 0x1BE, 0);
36 read(IN, my $buffer, 1);
37 my $bytebuff = unpack("C",$buffer);
38 if($bytebuff != 0x00 and $bytebuff != 0x80){
39 print "First partition does not appear valid! (", unpack("H*", $buffer), ") \n";
40 print " Continue?";
41 if(!(<> =~ /[Yy]/)){
42 print "Script terminated.\n";
43 exit(0);
44 }
45 }elsif($bytebuff == 0x00 ){
46 print "First partition is not bootable.\n";
47 }elsif($bytebuff == 0x80 ){
48 print "First partition is bootable.\n";
49 }
50
51
52 ###################################################
53 # check partition type (must be FAT32)
54 seek(IN, 0x1C2, 0);
55 read(IN, $buffer, 1);
56 my $bytebuff = unpack("C",$buffer);
57 if($bytebuff != 0x0C){
58 print " File System does not appear to be FAT32! (", unpack("H*", $buffer), ") \n";
59 print " Continue?";
60 if(!(<> =~ /[Yy]/)){
61 print "Script terminated.\n";
62 exit(0);
63 }
64 }elsif($bytebuff == 0x0C ){
65 print " File System Type: FAT32\n";
66 }
67
68
69 ###################################################
70 # get location of 1st partition
71 seek(IN, 0x1C6, 0);
72 read(IN, $buffer, 4);
73 my $Part1LocLBA = unpack("L",$buffer);
74 my $Part1BSLoc = $Part1LocLBA * 512;
75 my $buffer = scalar reverse pack("L",$Part1LocLBA);
76 my $buffer = uc unpack("H*",$buffer);
77 if($Part1LocLBA >= 0){
78 print " Located at: 0x", $buffer, " LBA (offset = 0x", uc unpack("H*", scalar reverse pack("L",$Part1BSLoc)), ")\n";
79 }else{
80 print " Partition location is INVALID!\n";
81 print " Continue?";
82 if(!(<> =~ /[Yy]/)){
83 print "Script terminated.\n";
84 exit(0);
85 }
86 }
87
88
89 ###################################################
90 # get size of 1st partition
91 seek(IN, 0x1CA, 0);
92 read(IN, $buffer, 4);
93 my $Part1Size = unpack("L",$buffer);
94 if($Part1Size >= 0){
95 print " Size: ", $Part1Size * 512, " Bytes \n";
96 }else{
97 print " Partition size is INVALID!\n";
98 print " Continue?";
99 if(!(<> =~ /[Yy]/)){
100 print "Script terminated.\n";
101 exit(0);
102 }
103 }
104
105 ###################################################
106 # Another simple test, confirm MBR sig
107 seek(IN, 0x1FE, 0);
108 read(IN, $buffer, 2);
109 my $buffer = unpack("S",$buffer);
110 if($buffer != 0xAA55){
111 print " MBR signature incorrect! (", uc unpack("H*", scalar reverse pack("S",$buffer)), ")\nContinue?";
112 if(!(<> =~ /[Yy]/)){
113 print "Script terminated.\n";
114 exit(0);
115 }
116 }
117
118
119 ###################################################
120 # examine boot sector of first partition
121 seek(IN, $Part1BSLoc + 0x03, 0);
122 read(IN, $buffer, 8);
123 print "\nExamining 1st Partition Boot Sector:\n OEM Name: ", $buffer, "\n";
124 # get bytes per sector
125 seek(IN, $Part1BSLoc + 0x0B, 0);
126 read(IN, $buffer, 2);
127 my $Part1BpS = unpack("S",$buffer);
128 if($Part1BpS > 0){
129 print " Bytes per sector: ", $Part1BpS, "\n";
130 }else{
131 print " Bytes per sector value is INVALID!\nContinue?";
132 if(!(<> =~ /[Yy]/)){
133 print "Script terminated.\n";
134 exit(0);
135 }
136 }
137
138
139 ###################################################
140 # get sectors per cluster
141 seek(IN, $Part1BSLoc + 0x0d, 0);
142 read(IN, $buffer, 1);
143 my $Part1SpC = unpack("C",$buffer);
144 if($Part1SpC > 0){
145 print " Sectors per cluster: ", $Part1SpC, " (", $Part1SpC * $Part1BpS, " Bytes/Cluster)\n";
146 }else{
147 print " Sectors per cluster value is INVALID!\nContinue?";
148 if(!(<> =~ /[Yy]/)){
149 print "Script terminated.\n";
150 exit(0);
151 }
152 }
153
154
155 ###################################################
156 # get first FAT offset
157 seek(IN, $Part1BSLoc + 0x0e, 0);
158 read(IN, $buffer, 2);
159 my $Part1FAT1Loc = unpack("S",$buffer);
160 my $Part1FAT1Loc = ($Part1FAT1Loc * $Part1BpS) + $Part1BSLoc;
161 my $buffer = scalar reverse pack("L",$Part1FAT1Loc);
162 my $buffer = uc scalar unpack("H*",$buffer);
163 if($Part1FAT1Loc > 0){
164 print " FAT #1 located at: 0x", $buffer, "\n";
165 }else{
166 print " FAT #1 location is INVALID!\nContinue?";
167 if(!(<> =~ /[Yy]/)){
168 print "Script terminated.\n";
169 exit(0);
170 }
171 }
172
173
174 ###################################################
175 # get number of FAT's
176 seek(IN, $Part1BSLoc + 0x10, 0);
177 read(IN, $buffer, 1);
178 my $Part1NumOfFATs = unpack("C",$buffer);
179 my $buffer = scalar reverse pack("S",$Part1NumOfFATs);
180 my $buffer = uc scalar unpack("H*",$buffer);
181 if($Part1NumOfFATs > 0){
182 print " Number of FAT's: ", $Part1NumOfFATs, "\n";
183 }else{
184 print " Number of FAT's is INVALID!\nContinue?";
185 if(!(<> =~ /[Yy]/)){
186 print "Script terminated.\n";
187 exit(0);
188 }
189 }
190
191
192 ###################################################
193 # Another simple test for FAT32, value should be 0!
194 seek(IN, $Part1BSLoc + 0x11, 0);
195 read(IN, $buffer, 2);
196 my $buffer = unpack("S",$buffer);
197 if($buffer != 0){
198 print " Invalid value detected in Boot Sector!\nContinue?";
199 if(!(<> =~ /[Yy]/)){
200 print "Script terminated.\n";
201 exit(0);
202 }
203 }
204
205
206 ###################################################
207 # Get total sectors
208 seek(IN, $Part1BSLoc + 0x13, 0);
209 read(IN, $buffer, 2);
210 my $Part1TotalSects = unpack("S",$buffer);
211 # if value is zero, size is at 0x20
212 if($Part1TotalSects == 0){
213 seek(IN, $Part1BSLoc + 0x20, 0);
214 read(IN, $buffer, 4);
215 $Part1TotalSects = unpack("L",$buffer);
216 if($Part1TotalSects > 0){
217 print " Total sectors: ", $Part1TotalSects, " (", $Part1TotalSects * $Part1BpS, " Bytes)\n";
218 if($Part1TotalSects == $Part1Size){
219 print " Partition size confirmed!\n";
220 }else{
221 print " Warning! Partition size mismatch! (MBR vs. BootSect)\n";
222 }
223 }else{
224 print " Total sectors value is INVALID!\nContinue?";
225 if(!(<> =~ /[Yy]/)){
226 print "Script terminated.\n";
227 exit(0);
228 }
229 }
230 }
231
232
233 ###################################################
234 # get sectors per FAT
235 seek(IN, $Part1BSLoc + 0x24, 0);
236 read(IN, $buffer, 4);
237 my $Part1SectpFAT = unpack("L",$buffer);
238 if($Part1SectpFAT > 0){
239 print " Sectors per FAT: ", $Part1SectpFAT, "\n";
240 }else{
241 print " Sectors per FAT's is INVALID!\nContinue?";
242 if(!(<> =~ /[Yy]/)){
243 print "Script terminated.\n";
244 exit(0);
245 }
246 }
247
248
249 ###################################################
250 # calculate FAT #2 location
251 if($Part1NumOfFATs > 1){
252 my $Part1FAT2Loc = $Part1FAT1Loc + $Part1SectpFAT * $Part1BpS;
253 my $buffer = scalar reverse pack("L",$Part1FAT2Loc);
254 my $buffer = uc scalar unpack("H*",$buffer);
255 print " FAT #2 located at: 0x", $buffer, "\n";
256 }
257
258
259 ###################################################
260 # get location of root directory
261 seek(IN, $Part1BSLoc + 0x2C, 0);
262 read(IN, $buffer, 4);
263 my $Part1RDLoc = unpack("L",$buffer); # normally this number seems to be 2 (ie: 3rd cluster)
264 # note, following cluster start location is calculation based on FAT sizes & number,
265 # which seems to be used as 3rd cluster location!
266 my $Part1CSLocCalcd = $Part1FAT1Loc + ($Part1NumOfFATs * $Part1SectpFAT * $Part1BpS);
267 # cluster start offset appears to be 2 clusters before DATA area:
268 my $Part1CSLoc = $Part1CSLocCalcd - (2 * $Part1SpC * $Part1BpS);
269 my $Part1RDLoc = $Part1CSLoc + $Part1RDLoc * $Part1SpC * $Part1BpS;
270 if($Part1RDLoc == $Part1CSLocCalcd){
271 print " Location of Root Directory: 0x", uc unpack("H*", scalar reverse pack("L",$Part1RDLoc)), "\n";
272 print " This is also the start of the partition DATA\n (cluster #2, realistically cluster #0)\n";
273 }else{
274 print " Cluster start location doesn't match script programming!\nContinue?";
275 if(!(<> =~ /[Yy]/)){
276 print "Script terminated.\n";
277 exit(0);
278 }
279 }
280
281 ###################################################
282 # get FS information location
283 seek(IN, $Part1BSLoc + 0x30, 0);
284 read(IN, $buffer, 2);
285 my $Part1FSLoc = unpack("S",$buffer) * $Part1BpS + $Part1BSLoc;
286 if($Part1FSLoc > 0){
287 print " FS Info located at: 0x", uc unpack("H*", scalar reverse pack("L",$Part1FSLoc)), "\n";
288 }else{
289 print " FS Info location is INVALID!\nContinue?";
290 if(!(<> =~ /[Yy]/)){
291 print "Script terminated.\n";
292 exit(0);
293 }
294 }
295
296
297 ###################################################
298 # get boot sector copy location
299 seek(IN, $Part1BSLoc + 0x32, 0);
300 read(IN, $buffer, 2);
301 my $Part1BSCopyLoc = unpack("S",$buffer) * $Part1BpS + $Part1BSLoc;
302 if($Part1FSLoc > 0 and $Part1FSLoc < $Part1CSLocCalcd){
303 print " Boot Sector copy located at: 0x", uc unpack("H*", scalar reverse pack("L",$Part1BSCopyLoc)), "\n";
304 }else{
305 print " Boot Sector copy location is INVALID!\nContinue?";
306 if(!(<> =~ /[Yy]/)){
307 print "Script terminated.\n";
308 exit(0);
309 }
310 }
311
312
313 ###################################################
314 # get partition volume label
315 seek(IN, $Part1BSLoc + 0x47, 0);
316 read(IN, $buffer, 11);
317 my $Part1Label = $buffer;
318 print " Partition Volume Label: ", $Part1Label, "\n";
319
320
321 ###################################################
322 # Another simple test for FAT32
323 seek(IN, $Part1BSLoc + 0x52, 0);
324 read(IN, $buffer, 8);
325 if(!($buffer =~ /FAT32/)){
326 print " Indicated FS Type is not FAT32! (", $buffer, ")\nContinue?";
327 if(!(<> =~ /[Yy]/)){
328 print "Script terminated.\n";
329 exit(0);
330 }
331 }
332
333 ###################################################
334 # Another simple test, confirm BS sig
335 seek(IN, $Part1BSLoc + 0x1FE, 0);
336 read(IN, $buffer, 2);
337 my $buffer = unpack("S",$buffer);
338 if($buffer != 0xAA55){
339 print " Boot Sector signature incorrect! (", uc unpack("H*", scalar reverse pack("S",$buffer)), ")\nContinue?";
340 if(!(<> =~ /[Yy]/)){
341 print "Script terminated.\n";
342 exit(0);
343 }
344 }
345
346
347 ###################################################
348 # compare both Boot Sectors
349 my $BSmismatch = 0;
350 for(my $i = 0; $i<512; $i++){
351
352 seek(IN, $Part1BSLoc + $i, 0);
353 read(IN, $buffer, 1);
354 my $buffer = unpack("C",$buffer);
355
356 seek(IN, $Part1BSCopyLoc + $i, 0);
357 read(IN, $bytebuff, 1);
358 my $bytebuff = unpack("C",$bytebuff);
359
360 if($buffer != $bytebuff){
361 $BSmismatch++;
362 print " BS mismatch detected! 0x";
363 print uc unpack("H*", scalar reverse pack("S",$Part1BSLoc + $i)), ": ";
364 print uc unpack("H*", scalar reverse pack("C",$buffer)), " vs ";
365 print uc unpack("H*", scalar reverse pack("C",$bytebuff)), " (0x";
366 print uc unpack("H*", scalar reverse pack("S",$Part1BSCopyLoc + $i)), ")\n";
367 }
368 }
369 if($BSmismatch != 0){
370 print " Boot Sector's do not match! ", $BSmismatch, " mismatches detected\nContinue?";
371 if(!(<> =~ /[Yy]/)){
372 print "Script terminated.\n";
373 exit(0);
374 }
375 }else{
376 print " Boot Sector matches backup Boot Sector!\n";
377 }
378
379
380 ###################################################
381 # Start analysing FAT
382 seek(IN, $Part1FAT1Loc + 0x04, 0);
383 read(IN, $buffer, 4);
384 my $Part1EOCval = unpack("L",$buffer) & 0x0FFFFFFF;
385 print "\nExamining FAT1:\n EOC Value: 0x", uc unpack("H*", scalar reverse pack("L",$Part1EOCval)), "\n";
386
387 # count used clusters
388 my $Part1EOCcount = 0;
389 print " Counting used clusters... ";
390 my $Part1FAT1BSize = $Part1SectpFAT * $Part1BpS;
391 for(my $i=8;$i < $Part1FAT1BSize; $i = $i + 4){
392 seek(IN, $Part1FAT1Loc + $i , 0);
393 read(IN, $buffer, 4);
394 my $buffer = unpack("L",$buffer);
395 if(($buffer & 0x0FFFFFFF) == 0x0FFFFFFF){
396 $Part1EOCcount++;
397 }
398 if($buffer == 0){
399 # confirm!
400 seek(IN, $Part1FAT1Loc + $i + 4 , 0);
401 read(IN, $buffer, 4);
402 $buffer = unpack("L",$buffer);
403 my $Part1FAT1CU = ($i/4) - 1;
404 print $Part1FAT1CU, " (", $Part1FAT1CU * $Part1SpC * $Part1BpS / 1024 / 1024," MBytes)\n";
405 if($buffer != 0){
406 print " Detected unfree cluster after free cluster!\n";
407 }
408 last;
409 }
410 }
411 # display FAT chains count
412 print " Number of FAT chains: ", $Part1EOCcount, " (non-deleted & deleted)\n";
413
414 ###################################################
415 # Follow chains
416 #@Part1Chains
417
418 my $CIndex = 0;
419 my $i = 8;
420 my $PrvChainLoc = $i;
421 my @Part1Chains = (0,0);
422 my @Part1ClustMap = (1,1);
423 for(my $itmp = 2;$itmp < $Part1FAT1BSize/4; $itmp++){
424 $Part1ClustMap[$itmp] = 0;
425 }
426 for($CIndex=0;$CIndex < $Part1EOCcount; $PrvChainLoc = $PrvChainLoc + 4){
427 if($Part1ClustMap[$i/4] != 1){
428 # print "warning! duplicate cluster in use!\n";
429 for($i=$PrvChainLoc;$i < $Part1FAT1BSize and $Part1ClustMap[$i/4] != 1; ){
430 seek(IN, $Part1FAT1Loc + $i , 0);
431 read(IN, $buffer, 4);
432 my $buffer = unpack("L",$buffer);
433 if(($buffer & 0x0FFFFFFF) == 0x0FFFFFFF){
434 $Part1ClustMap[$i/4] = 1;
435 $Part1Chains[$CIndex] = $Part1Chains[$CIndex] + 1;
436 print $Part1Chains[$CIndex], " =Chain size (", $Part1Chains[$CIndex] * $Part1SpC * $Part1BpS / 1024, "KBytes)\n";
437 $CIndex++;
438 $Part1Chains[$CIndex] = 0;
439 last;
440 }
441 if($buffer == 0){
442 last;
443 }
444 $Part1ClustMap[$i/4] = 1;
445 $Part1Chains[$CIndex]++;
446 my $i = $buffer * 4;
447
448 }
449 }
450 }
451 #print $Part1Chains[$CIndex-1], "\n";
452
453
454
455 # Useful Values:
456 #
457 # $Part1FAT1BSize
458 # $Part1EOCval
459 # $Part1BSCopyLoc
460 # $Part1FSLoc
461 # $Part1CSLocCalcd - calculated location of start of clusters (clusters 0 & 1 seem missing! Intentional?)
462 # $Part1CSLoc - location of start of clusters (adjusted based on physically observed location of Root Dir)
463 # (confirm this with known good FAT32 disks)
464 # $Part1RDLoc - location of root directory
465 # $Part1NumOfFATs
466 # $Part1Size & $Part1TotalSects
467 # $Part1BpS
468 # $Part1SpC
469 # $Part1BSLoc
470 # $Part1FAT1Loc
471 # $Part1SectpFAT
472 # $Part1RDLoc
473
474
475
476
477
478 close IN;
479 print "\n\nDONE!\n";
480
481 exit(0);