Skip to content

Instantly share code, notes, and snippets.

What would you like to do?
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
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):
def putRawBytes(self, pos, data):
def putUInt32(self, pos, n):
print 'write %10s@%08x' % (n, pos)
self.putRawBytes(pos, struct.pack('<i', n))
def putUInt16(self, pos, n):
self.putRawBytes(pos, struct.pack('<H', n))
def putUInt8(self, pos, n):
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"
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]
def bytesPerSector(self):
return self.getBytes(11,2)
def sectorsPerCluster(self):
return self.getBytes(13,1)
def bytesPerCluster(self):
return self.bytesPerSector * self.sectorsPerCluster
def reservedSectorCount(self):
return self.getBytes(14,2)
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)
def FATSize(self):
return self.getBytes(36,4)
def rootStart(self):
return self.FATStart(1) + (self.FATSize * self.numberOfFATs * self.bytesPerSector)
def fsIdentityString(self):
return self.getString(82,8)
class ClEntry:
def __init__(self, offset, length, nextclidx, prevclidx=None):
self.offset = offset
self.length = length = nextclidx
self.prev = prevclidx
def __repr__(self):
return 'ClEnt(%s, %s, %s, %s)' % (self.offset, self.length,, self.prev)
class Clusters:
def __init__(self, fs):
self.fs = fs
self.fat = [] = set(), 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:
nextcluster = self.fs.getBytes(pos, 4)
self.fat.append(ClEntry(offset, self.fs.bytesPerCluster, nextcluster))
if nextcluster == 0:
for clidx in xrange(0, len(self.fat)):
cluster = self.fat[clidx]
if > 2 and < 0xffffff8:
self.fat[].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
if value < 0xffffff8:
# set the previous cluster reference
self.fat[value].prev = target
# remove cluster from the free list
def swap(self, clidx1, clidx2):
c1 = self.fat[clidx1]
c2 = self.fat[clidx2]
n1 =
n2 =
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)
for arg in sys.argv[2:]:
clidx = clusters.fat[int(arg)].next
while clidx > 2 and clidx < 0xffffff8:
free = random.sample(, 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)
Copy link

vladimircicovic commented Aug 20, 2015


Copy link

divmgl commented Sep 4, 2015

This is awesome

Copy link

ryancdotorg commented Sep 10, 2015

Hi hackernews and /r/programming! My blog is over here (I've been working on posting more often) and I'm on twitter as @ryancdotorg.

Copy link

visy commented Sep 10, 2015

This is kinda silly but who knows, it might be usable for something. Maybe a defragmenter benchmark :)

Copy link

FesterCluck commented Sep 10, 2015

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.

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 ...

Copy link

spankr commented Sep 10, 2015

That is a thing of beauty!

Copy link

ryancdotorg commented Sep 10, 2015

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

Copy link

el-chogo commented May 12, 2016

No jQuery :c.

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