Class: NTFS::BootSect

Inherits:
Object
  • Object
show all
Defined in:
lib/fs/ntfs/boot_sect.rb

Overview

BootSect represents a volume boot sector.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(stream) ⇒ BootSect

Returns a new instance of BootSect.



62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/fs/ntfs/boot_sect.rb', line 62

def initialize(stream)
  raise "MIQ(NTFS::BootSect.initialize) Nil stream" if stream.nil?

  # Buffer stream & get enough data to fill BPB.
  @stream = stream
  buf     = stream.read(SIZEOF_BOOT_PARAMETERS_BLOCK)
  @bpb    = BOOT_PARAMETERS_BLOCK.decode(buf)

  # Always check magic number first.
  @signature = @bpb['signature']
  raise "MIQ(NTFS::BootSect.initialize) Boot sector is not NTFS: 0x#{'%04x' % signature}" if signature != NTFS_MAGIC

  # Get accessor values.
  @bytesPerSector    = @bpb['bytes_per_sector']
  @bytesPerCluster   = @bpb['sectors_per_cluster'] * @bytesPerSector
  @sectorsPerCluster = @bpb['sectors_per_cluster']
  @mediaDescriptor   = @bpb['media_descriptor']
  @totalCapacity     = @bpb['sectors_per_volume'] * @bytesPerSector
  @bytesPerFileRec   = bytesPerRec(@bpb['clusters_per_mft_record'])
  @bytesPerIndexRec  = bytesPerRec(@bpb['clusters_per_index_record'])
  @serialNumber      = @bpb['volume_serial_number']

  # MFTs in-memory
  @sys_mfts    = {}
  @mfts        = LruHash.new(NTFS::DEF_CACHE_SIZE)
end

Instance Attribute Details

#bytesPerClusterObject (readonly)

Returns the value of attribute bytesPerCluster.



58
59
60
# File 'lib/fs/ntfs/boot_sect.rb', line 58

def bytesPerCluster
  @bytesPerCluster
end

#bytesPerFileRecObject (readonly)

Returns the value of attribute bytesPerFileRec.



57
58
59
# File 'lib/fs/ntfs/boot_sect.rb', line 57

def bytesPerFileRec
  @bytesPerFileRec
end

#bytesPerIndexRecObject (readonly)

Returns the value of attribute bytesPerIndexRec.



57
58
59
# File 'lib/fs/ntfs/boot_sect.rb', line 57

def bytesPerIndexRec
  @bytesPerIndexRec
end

#bytesPerSectorObject (readonly)

Returns the value of attribute bytesPerSector.



56
57
58
# File 'lib/fs/ntfs/boot_sect.rb', line 56

def bytesPerSector
  @bytesPerSector
end

#mediaDescriptorObject (readonly)

Returns the value of attribute mediaDescriptor.



56
57
58
# File 'lib/fs/ntfs/boot_sect.rb', line 56

def mediaDescriptor
  @mediaDescriptor
end

#sectorsPerClusterObject (readonly)

Returns the value of attribute sectorsPerCluster.



56
57
58
# File 'lib/fs/ntfs/boot_sect.rb', line 56

def sectorsPerCluster
  @sectorsPerCluster
end

#serialNumberObject (readonly)

Returns the value of attribute serialNumber.



57
58
59
# File 'lib/fs/ntfs/boot_sect.rb', line 57

def serialNumber
  @serialNumber
end

#signatureObject (readonly)

Returns the value of attribute signature.



58
59
60
# File 'lib/fs/ntfs/boot_sect.rb', line 58

def signature
  @signature
end

#streamObject (readonly)

Returns the value of attribute stream.



56
57
58
# File 'lib/fs/ntfs/boot_sect.rb', line 56

def stream
  @stream
end

#totalCapacityObject (readonly)

Returns the value of attribute totalCapacity.



57
58
59
# File 'lib/fs/ntfs/boot_sect.rb', line 57

def totalCapacity
  @totalCapacity
end

#versionObject

Returns the value of attribute version.



60
61
62
# File 'lib/fs/ntfs/boot_sect.rb', line 60

def version
  @version
end

#volumeInfoObject

Returns the value of attribute volumeInfo.



60
61
62
# File 'lib/fs/ntfs/boot_sect.rb', line 60

def volumeInfo
  @volumeInfo
end

Instance Method Details

#bytesPerRec(size) ⇒ Object

NTFS has an interesting shorthand…



95
96
97
# File 'lib/fs/ntfs/boot_sect.rb', line 95

def bytesPerRec(size)
  (size < 0) ? 2**size.abs : size * bytesPerCluster
end

#clusterInfoObject

From “File System Forensic Analysis” by Brian Carrier

The $Bitmap file, which is located in MFT entry 6, has a $DATA attribute that is used to manage the allocation status of clusters. The bitmap data are organized into 1-byte values, and the least significant bit of each byte corresponds to the cluster that follows the cluster that the most significant bit of the previous byte corresponds to.



135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
# File 'lib/fs/ntfs/boot_sect.rb', line 135

def clusterInfo
  return @clusterInfo unless @clusterInfo.nil?

  # MFT Entry 6 ==> BITMAP Information
  ad = mftEntry(6).attributeData
  data = ad.read(ad.length)
  ad.rewind

  c = data.unpack("b#{data.length * 8}")[0]
  nclusters = c.length
  on = c.count("1")
  uclusters = on
  fclusters = c.length - on

  @clusterInfo = {"total" => nclusters, "free" => fclusters, "used" => uclusters}
end

#fragTableObject



104
105
106
# File 'lib/fs/ntfs/boot_sect.rb', line 104

def fragTable
  @fragTable || @rootFragTable
end

#freeBytesObject

Returns free space on file system in bytes.



153
154
155
# File 'lib/fs/ntfs/boot_sect.rb', line 153

def freeBytes
  clusterInfo["free"] * @bytesPerCluster
end

#getMaxMftObject

Iterate all run lengths & return how many entries fit.



185
186
187
188
189
# File 'lib/fs/ntfs/boot_sect.rb', line 185

def getMaxMft
  total_clusters = 0
  fragTable.each_slice(2) { |_vcn, len| total_clusters += len }
  total_clusters * @bytesPerCluster / @bytesPerFileRec
end

#getVolumeInfoObject



157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
# File 'lib/fs/ntfs/boot_sect.rb', line 157

def getVolumeInfo
  mft = mftEntry(3)
  vi  = {}

  if nameAttrib = mft.getFirstAttribute(AT_VOLUME_NAME)
    vi["name"] = nameAttrib.name
  end

  if objectidAttrib = mft.getFirstAttribute(AT_OBJECT_ID)
    vi["objectId"]      = objectidAttrib.objectId.to_s
    vi["birthVolumeId"] = objectidAttrib.birthVolumeId.to_s
    vi["birthObjectId"] = objectidAttrib.birthObjectId.to_s
    vi["domainId"]      = objectidAttrib.domainId.to_s
  end

  if infoAttrib = mft.getFirstAttribute(AT_VOLUME_INFORMATION)
    vi["version"] = infoAttrib.version
    vi["flags"]   = infoAttrib.flags
  end

  vi
end

#isMountable?Boolean

Quick check to see if volume is mountable.

Returns:

  • (Boolean)


210
211
212
213
214
215
216
217
# File 'lib/fs/ntfs/boot_sect.rb', line 210

def isMountable?
  return false if @bpb.nil?
  b  = @bpb['reserved_sectors'] == 0
  b &= @bpb['unused1'] == "\0" * 5
  b &= @bpb['unused2'] == 0
  b &= @bpb['unused4'] == 0
  b &= @bpb['signature'] == 0xaa55
end

#lcn2abs(lcn) ⇒ Object

Convert a logical cluster number to an absolute byte position.



220
221
222
# File 'lib/fs/ntfs/boot_sect.rb', line 220

def lcn2abs(lcn)
  lcn * bytesPerCluster
end

#maxMftObject



108
109
110
111
# File 'lib/fs/ntfs/boot_sect.rb', line 108

def maxMft
  return getMaxMft if @fragTable.nil?
  @maxMft ||= getMaxMft
end

#mftEntry(recordNumber) ⇒ Object



195
196
197
198
199
200
201
202
203
204
205
206
207
# File 'lib/fs/ntfs/boot_sect.rb', line 195

def mftEntry(recordNumber)
  if recordNumber < 12
    @sys_mfts[recordNumber] = MftEntry.new(self, recordNumber) unless @sys_mfts.key?(recordNumber)
    return @sys_mfts[recordNumber]
  end

  if @mfts.key?(recordNumber)
    mft = @mfts[recordNumber]
    mft.attributeData.rewind unless mft.attributeData.nil?
    return mft
  end
  @mfts[recordNumber] = MftEntry.new(self, recordNumber)
end

#mftLocObject

Return the absolute byte position of the MFT.



100
101
102
# File 'lib/fs/ntfs/boot_sect.rb', line 100

def mftLoc
  @bpb.nil? ? 0 : lcn2abs(@bpb['mft_lcn'])
end

#mftRecToBytePos(recno) ⇒ Object

Use data run to convert mft record number to byte pos.



230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
# File 'lib/fs/ntfs/boot_sect.rb', line 230

def mftRecToBytePos(recno)
  # Return start of mft if rec 0 (no point in the rest of this).
  return mftLoc if recno == 0

  # Find which fragment contains the target mft record.
  start = fragTable[0]; last_clusters = 0; target_cluster = recno * @bytesPerFileRec / @bytesPerCluster
  if (recno > @bytesPerCluster / @bytesPerFileRec) && (fragTable.size > 2)
    total_clusters = 0
    fragTable.each_slice(2) do |vcn, len|
      start = vcn # These are now absolute clusters, not offsets.
      total_clusters += len
      break if total_clusters > target_cluster
      last_clusters += len
    end
    # Toss if we haven't found the fragment.
    raise "MIQ(NTFS::BootSect.mftRecToBytePos) Can't find MFT record #{recno} in data run.\ntarget = #{target_cluster}\ntbl = #{fragTable.inspect}" if total_clusters < target_cluster
  end

  # Calculate offset in target cluster & final byte position.
  offset = (recno - (last_clusters * @bytesPerCluster / @bytesPerFileRec)) * @bytesPerFileRec
  start * @bytesPerCluster + offset
end

#numFragsObject



180
181
182
# File 'lib/fs/ntfs/boot_sect.rb', line 180

def numFrags
  fragTable.size / 2
end

#rootDirObject



191
192
193
# File 'lib/fs/ntfs/boot_sect.rb', line 191

def rootDir
  @rootDir ||= mftEntry(5).indexRoot
end

#setupObject



113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/fs/ntfs/boot_sect.rb', line 113

def setup
  @rootFragTable = mftEntry(0).rootAttributeData.data.runSpec

  @sys_mfts.clear
  @mfts.clear

  # MFT Entry 0 ==> Prepare a fragment table.
  @fragTable  = mftEntry(0).attributeData.data.runSpec   # Get the data runs for the MFT itself.

  # MFT Entry 3 ==> Volume Information
  @volumeInfo = getVolumeInfo
  @version    = @volumeInfo["version"].to_i
end

#to_sObject

Convert to string (just return OEM name).



90
91
92
# File 'lib/fs/ntfs/boot_sect.rb', line 90

def to_s
  @bpb['oem_name'].strip
end

#vcn2abs(vcn) ⇒ Object

Convert a virtual cluster number to an absolute byte position.



225
226
227
# File 'lib/fs/ntfs/boot_sect.rb', line 225

def vcn2abs(vcn)
  lcn2abs(vcn)
end