Skip to content

Instantly share code, notes, and snippets.

@ryancdotorg
Created August 20, 2015 16:27
Show Gist options
  • Save ryancdotorg/9f557d3513710ce91aed to your computer and use it in GitHub Desktop.
Save ryancdotorg/9f557d3513710ce91aed to your computer and use it in GitHub Desktop.
A FAT32 fragmenter, because I am a horrible person.
#!/usr/bin/env python
import random
import struct
import sys
# Most of the Fat32 class was cribbed from https://gist.github.com/jonte/4577833
def ppNum(num):
return "%s (%s)" % (hex(num), num)
class Fat32:
def __init__(self, filename):
self.fs = open(filename, 'r+b')
def getRawBytes(self, pos, numBytes):
self.fs.seek(pos)
return self.fs.read(numBytes)
def putRawBytes(self, pos, data):
self.fs.seek(pos)
self.fs.write(data)
def putUInt32(self, pos, n):
print 'write %10s@%08x' % (n, pos)
self.fs.seek(pos)
self.putRawBytes(pos, struct.pack('<i', n))
def putUInt16(self, pos, n):
self.fs.seek(pos)
self.putRawBytes(pos, struct.pack('<H', n))
def putUInt8(self, pos, n):
self.fs.seek(pos)
self.putRawBytes(pos, struct.pack('<B', n))
def getBytes(self, pos, numBytes):
byte = self.getRawBytes(pos, numBytes)
if (numBytes == 2):
formatString = "H"
elif (numBytes == 1):
formatString = "B"
elif (numBytes == 4):
formatString = "i"
else:
raise Exception("Not implemented")
return struct.unpack("<"+formatString, byte)[0]
def getString(self, pos, numBytes):
raw = self.getRawBytes(pos, numBytes)
return struct.unpack(str(numBytes)+"s", raw)[0]
@property
def bytesPerSector(self):
return self.getBytes(11,2)
@property
def sectorsPerCluster(self):
return self.getBytes(13,1)
@property
def bytesPerCluster(self):
return self.bytesPerSector * self.sectorsPerCluster
@property
def reservedSectorCount(self):
return self.getBytes(14,2)
@property
def numberOfFATs(self):
return self.getBytes(16,1)
def FATStart(self, numFat):
return self.reservedSectorCount * self.bytesPerSector + (self.FATSize * self.bytesPerSector * (numFat - 1))
def FATEnd(self, numFat):
return self.FATStart(numFat+1)
@property
def FATSize(self):
return self.getBytes(36,4)
@property
def rootStart(self):
return self.FATStart(1) + (self.FATSize * self.numberOfFATs * self.bytesPerSector)
@property
def fsIdentityString(self):
return self.getString(82,8)
class ClEntry:
def __init__(self, offset, length, nextclidx, prevclidx=None):
self.offset = offset
self.length = length
self.next = nextclidx
self.prev = prevclidx
def __repr__(self):
return 'ClEnt(%s, %s, %s, %s)' % (self.offset, self.length, self.next, self.prev)
class Clusters:
def __init__(self, fs):
self.fs = fs
self.fat = []
self.free = set()
self.fs.fs.seek(0, 2)
eof = self.fs.fs.tell()
for pos in range(self.fs.FATStart(1), self.fs.FATEnd(1), 4):
clidx = (pos - self.fs.FATStart(1)) / 4
# not entirely sure why -2 is required here
offset = self.fs.rootStart + (clidx-2) * self.fs.bytesPerCluster
if offset + self.fs.bytesPerCluster >= eof:
break
nextcluster = self.fs.getBytes(pos, 4)
self.fat.append(ClEntry(offset, self.fs.bytesPerCluster, nextcluster))
if nextcluster == 0:
self.free.add(clidx)
for clidx in xrange(0, len(self.fat)):
cluster = self.fat[clidx]
if cluster.next > 2 and cluster.next < 0xffffff8:
self.fat[cluster.next].prev = clidx
def setNextCluster(self, target, value):
if value is None:
value = 0
if target is not None:
writeOffset = self.fs.FATStart(1) + target * 4
self.fs.putUInt32(writeOffset, value)
self.fat[target].next = value
if value == 0:
# clear the previous cluster reference
self.fat[target].prev = None
# add cluster to the free list
self.free.add(target)
else:
if value < 0xffffff8:
# set the previous cluster reference
self.fat[value].prev = target
# remove cluster from the free list
self.free.discard(target)
def swap(self, clidx1, clidx2):
c1 = self.fat[clidx1]
c2 = self.fat[clidx2]
n1 = c1.next
n2 = c2.next
p1 = c1.prev
p2 = c2.prev
o1 = c1.offset
o2 = c2.offset
d1 = self.fs.getRawBytes(o1, c1.length)
d2 = self.fs.getRawBytes(o2, c2.length)
self.setNextCluster(p1, clidx2)
self.setNextCluster(clidx2, n1)
self.setNextCluster(p2, clidx1)
self.setNextCluster(clidx1, n2)
print o1
print o2
self.fs.putRawBytes(o1, d2)
self.fs.putRawBytes(o2, d1)
fs = Fat32(sys.argv[1])
print "Bytes per sector:", ppNum(fs.bytesPerSector)
print "Sectors per cluster:", ppNum(fs.sectorsPerCluster)
print "Reserved sector count:", ppNum(fs.reservedSectorCount)
print "Size of FAT (sectors):", ppNum(fs.FATSize)
print "Number of FATs:", ppNum(fs.numberOfFATs)
print "Start of FAT1:", ppNum(fs.FATStart(1))
print "Start of root directory:", ppNum(fs.rootStart)
print "Identity string:", fs.fsIdentityString
clusters = Clusters(fs)
if 0:
clusters.swap(4, 6)
clusters.fs.fs.flush()
sys.exit(0)
for arg in sys.argv[2:]:
clidx = clusters.fat[int(arg)].next
while clidx > 2 and clidx < 0xffffff8:
free = random.sample(clusters.free, 1)[0]
nextidx = clusters.fat[clidx].next
print 'swap %10s <=> %10s next %10s' % (clidx, free, nextidx)
clusters.swap(clidx, free)
clidx = nextidx
#print clusters.fat
#clusters.swap(5, 9)
#clusters.swap(6, 10)
clusters.fs.fs.flush()
@FesterCluck
Copy link

Regarding the fragmentation method: what theme was it written with in mind? Maximal fragmentation? Parity with natural fragmentation? Performance?

I could see this used in security research to emulate data that hides itself in dark drive space. Developing a model for what natural fragmentation looks like would help malware evade heuristics.

@stuaxo
Copy link

stuaxo commented Sep 10, 2015

Themes, that is an interesting idea .. making one that could display ascii art into the older windows or dos defragmenter would be an interesting theme ...

@spankr
Copy link

spankr commented Sep 10, 2015

That is a thing of beauty!

@ryancdotorg
Copy link
Author

@FesterCluck Maximal fragmentation of a single file (It currently can't move the first cluster, though).

@el-chogo
Copy link

No jQuery :c.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment