Skip to content

Instantly share code, notes, and snippets.

@kou1okada
Last active June 10, 2016 12:17
Show Gist options
  • Save kou1okada/17f14f48f7524867bedef090362fab47 to your computer and use it in GitHub Desktop.
Save kou1okada/17f14f48f7524867bedef090362fab47 to your computer and use it in GitHub Desktop.
NTFS Dynamic Disk Information tool
#!/usr/bin/env ruby
#
# ntfsddinfo.rb - NTFS Dynamic Disk infomation tool.
# Copyright (c) 2016 Koichi OKADA. All rights reserved.
# This script is distributed under the MIT license.
#
require 'optparse'
$conf = {}
opts = OptionParser.new
opts.banner = "Usage: #{File.basename $0} target"
opts.on("-t", "--thousand-sep") {|v| $conf[:thousand_sep] = v}
opts.parse! ARGV
if ARGV.length != 1
puts opts.help
exit
end
target = ARGV[0]
if $conf[:thousand_sep]
def ts x; x.to_s =~ /^([^.]*\d)(\d{3}(\.|$))/ ? ts($1)+","+$2+$' : x; end
else
def ts x; x; end
end
def read_item(fp, offset, key, name)
#p (offset + DSTR[key][name][0])
fp.seek(offset + DSTR[key][name][0])
v = fp.read(DSTR[key][name][1])
v = v.method(DSTR[key][name][2]).call unless DSTR[key][name][2].nil?
v
end
def read_itemp(fp)
n = fp.read(1).unpack("C")[0]
v = fp.read(n)
end
def read_items(fp, offset, dstr)
p = 0
ret = {}
dstr.each{|name,val|
unless val[0].nil?
fp.seek(offset + val[0])
end
if val[1] == "P"
ret[name] = read_itemp(fp)
else
ret[name] = fp.read(val[1])
end
unless val[2].nil?
ret[name] = ret[name].method(val[2]).call
end
}
ret
end
class String
def to_uint64be
be = [1].pack("s") == "\x00\x01"
(be ? self.unpack("Q") : self.reverse.unpack("Q"))[0]
end
def to_ube
self.unpack("C*").inject(0){|r,v|
r = r << 8 | v
}
end
end
SEC_SIZE = 512
LDM_DATABASE_SIZE = 1024**2
TOCBLOCK_SIZE = 1024**2
VBLK_SIZE = 128
VBDM_SIZE = VBLK_SIZE * 4
DSTR = {
:PRIVHEAD => {
:DISK_ID_GUID => [0x30, 64, :strip],
:HOST_ID_GUID => [0x70, 64, :strip],
:LOGICAL_DISK_START => [0x11b, 8, :to_uint64be],
:LOGICAL_DISK_SIZE => [0x123, 8, :to_uint64be],
:CONFIGURATION_START => [0x12b, 8, :to_uint64be],
},
:TOCBLOCK => {
:BITMAP1_NAME => [0x24, 8, :strip],
:BITMAP1_FLAGS1 => [0x2C, 2, :to_ube],
:BITMAP1_START => [0x2E, 8, :to_ube],
:BITMAP1_SIZE => [0x36, 8, :to_ube],
:BITMAP1_FLAGS2 => [0x3e, 8, :to_ube],
:BITMAP2_NAME => [0x46, 8, :strip],
:BITMAP2_FLAGS1 => [0x4e, 2, :to_ube],
:BITMAP2_START => [0x50, 8, :to_ube],
:BITMAP2_SIZE => [0x58, 8, :to_ube],
:BITMAP2_FLAGS2 => [0x60, 8, :to_ube],
},
:VBLK => {
:MAGICK_NUMBER => [0x00, 4],
:SEQUENCE_NUMBER => [0x04, 4, :to_ube],
:GROUP_NUMBER => [0x08, 4, :to_ube],
:RECORD_NUMBER => [0x0c, 2, :to_ube],
:NUMBER_OF_RECORD => [0x0e, 2, :to_ube],
:UPDATE_STATUS => [0x10, 2, :to_ube],
:RECORD_TYPE_AND_FLAG => [0x12, 2, :to_ube],
:DATA_LENGTH => [0x14, 4, :to_ube],
}
}
VBLK_RECORD_TYPE = {
0x32 => "Component",
0x33 => "Partition",
0x34 => "Disk",
0x44 => "Disk",
0x35 => "DiskGroup",
0x45 => "DiskGroup",
0x51 => "Volume",
}
VBLK_DSTR = {
0x32 => { # Component
:OBJECT_ID => [0x18, "P", :to_ube],
:NAME => [nil , "P", :strip],
:VOLUME_STATE => [nil , "P", :strip],
:COMPONENT_TYPE => [nil , 1, :to_ube],
:ZEROS => [nil , 4, :to_ube],
:NUMBER_OF_CHILDREN => [nil , "P", :to_ube],
:LOG_COMMIT_ID => [nil , 8, :to_ube],
:ZEROS2 => [nil , 8, :to_ube],
:PARENT_ID => [nil , "P", :to_ube],
:ZEROS2 => [nil , 1, :to_ube],
:STRIP_SIZE => [nil , "P", :to_ube],
:NUMBER_OF_COLUMNS => [nil , "P", :to_ube],
},
0x33 => { # Partition
:OBJECT_ID => [0x18, "P", :to_ube],
:NAME => [nil , "P", :strip],
:ZEROS => [nil , 4, :to_ube],
:LOG_COMMIT_ID => [nil , 8, :to_ube],
:START => [nil , 8, :to_ube],
:VOLUME_OFFSET => [nil , 8, :to_ube],
:SIZE => [nil , "P", :to_ube],
:PARENTS_OBJECT_ID => [nil , "P", :to_ube],
:DISK_OBJECTS_ID => [nil , "P", :to_ube],
:COMPONENT_PART_INDEX => [nil , "P", :to_ube],
},
0x34 => { # Disk
:OBJECT_ID => [0x18, "P", :to_ube],
:NAME => [nil , "P", :strip],
:DISK_ID_GUID => [nil , "P", :strip],
:ALTERNATE_NAME => [nil , "P", :to_ube], #
:ZEROS => [nil , 4, :to_ube],
:LOG_COMMIT_ID => [nil , 8, :to_ube],
},
0x35 => { # DiskGroup
:OBJECT_ID => [0x18, "P", :to_ube],
:NAME => [nil , "P", :strip],
:DISK_GROUP_ID_GUID => [nil , "P", :strip],
:ZEROS => [nil , 4, :to_ube],
:LOG_COMMIT_ID => [nil , 8, :to_ube],
:_0xFFFFFFFF_1 => [nil , "P", :to_ube],
:_0xFFFFFFFF_2 => [nil , "P", :to_ube],
},
0x51 => { # Volume
:OBJECT_ID => [0x18, "P", :to_ube],
:NAME => [nil , "P", :strip],
:VOLUME_TYPE => [nil , "P", :strip],
:ZERO => [nil , "P", :strip],
:VOLUME_STATE => [nil , 14, :strip],
:VOLUME_TYPE2 => [nil , 1, :to_ube],
:DONTKNOW => [nil , 1, :to_ube],
:VOLUME_NUMBER => [nil , 1, :to_ube],
:ZEROS => [nil , 3, :to_ube],
:FLAGS => [nil , 1, :to_ube],
:NUMBER_OF_CHILDREN => [nil , "P", :to_ube],
:LOG_COMMIT_ID => [nil , 8, :to_ube],
:ID => [nil , 8, :to_ube],
:SIZE => [nil , "P", :to_ube],
:ZEROS2 => [nil , 4, :to_ube],
:PARTITTION_TYPE => [nil , 1, :to_ube],
:VOLUME_ID_GUID => [nil , 16, :to_ube],
# :ID1 => [nil , "P", :to_ube],
# :ID2 => [nil , "P", :to_ube],
# :SIZE => [nil , "P", :to_ube],
:DRIVE_HINT => [nil , "P", :strip],
},
}
open(target, "rb") do |fp|
fp.seek(0, IO::SEEK_END)
size = fp.tell
total_sec = size / SEC_SIZE
ldm_database_pos = [size - LDM_DATABASE_SIZE]
tocblock_pos = [ldm_database_pos[0] + 2 * SEC_SIZE]
privhead_pos = [6 * SEC_SIZE, size - 192 * SEC_SIZE, size - 1 * SEC_SIZE]
disk_id_guid = read_item(fp, privhead_pos[0], :PRIVHEAD, :DISK_ID_GUID)
host_id_guid = read_item(fp, privhead_pos[0], :PRIVHEAD, :HOST_ID_GUID)
logical_disk_start = read_item(fp, privhead_pos[0], :PRIVHEAD, :LOGICAL_DISK_START )
logical_disk_size = read_item(fp, privhead_pos[0], :PRIVHEAD, :LOGICAL_DISK_SIZE )
configuration_start = read_item(fp, privhead_pos[0], :PRIVHEAD, :CONFIGURATION_START)
printf("DISK_ID_GUID : %s\n", disk_id_guid);
printf("HOST_ID_GUID : %s\n", host_id_guid);
printf("TOTAL_SEC : %016x %20s\n", total_sec, ts(total_sec));
printf("LOGICAL_DISK_START : %016x %20s\n", logical_disk_start, ts(logical_disk_start));
printf("LOGICAL_DISK_SIZE : %016x %20s\n", logical_disk_size, ts(logical_disk_size));
printf("CONFIGURATION_START : %016x %20s\n", configuration_start, ts(configuration_start));
toc_bitmap1_name = read_item(fp, tocblock_pos[0], :TOCBLOCK, :BITMAP1_NAME)
toc_bitmap1_start = read_item(fp, tocblock_pos[0], :TOCBLOCK, :BITMAP1_START)
toc_bitmap1_size = read_item(fp, tocblock_pos[0], :TOCBLOCK, :BITMAP1_SIZE )
toc_bitmap2_name = read_item(fp, tocblock_pos[0], :TOCBLOCK, :BITMAP2_NAME)
toc_bitmap2_start = read_item(fp, tocblock_pos[0], :TOCBLOCK, :BITMAP2_START)
toc_bitmap2_size = read_item(fp, tocblock_pos[0], :TOCBLOCK, :BITMAP2_SIZE )
toc_bitmap = {
toc_bitmap1_name => {:START => toc_bitmap1_start, :SIZE => toc_bitmap1_size},
toc_bitmap2_name => {:START => toc_bitmap2_start, :SIZE => toc_bitmap2_size},
}
printf("BITMAP1_NAME : %s\n", toc_bitmap1_name);
printf("BITMAP1_START : %016x %20s\n", toc_bitmap1_start, ts(toc_bitmap1_start));
printf("BITMAP1_SIZE : %016x %20s\n", toc_bitmap1_size , ts(toc_bitmap1_size));
printf("BITMAP2_NAME : %s\n", toc_bitmap2_name);
printf("BITMAP2_START : %016x %20s\n", toc_bitmap2_start, ts(toc_bitmap2_start));
printf("BITMAP2_SIZE : %016x %20s\n", toc_bitmap2_size , ts(toc_bitmap2_size));
toc_bitmap.each {|key,val|
printf("%-8s : %4s + %4s\n", key, val[:START], val[:SIZE]);
}
vblk_pos = [ldm_database_pos[0] + toc_bitmap["config"][:START] * SEC_SIZE]
vblk_num = [toc_bitmap["config"][:SIZE] * SEC_SIZE / VBLK_SIZE]
printf("VBLK_NUM : %016x %20s\n", vblk_num[0], ts(vblk_num[0]));
(4...vblk_num[0]).each{|i|
vblk = read_items(fp, vblk_pos[0] + i * VBLK_SIZE, DSTR[:VBLK])
record_type_and_flag = vblk[:RECORD_TYPE_AND_FLAG]
record_type = record_type_and_flag & 0xff
#DSTR[:VBLK].each_key{|key|
# vblk[key] = read_item(fp, vblk_pos[0] + i * VBLK_SIZE, :VBLK, key)
#}
if record_type != 0
puts "="*70
printf("%s seq:%08x grp:%08x rec:%04x num:%04x upd:%04x\n",
vblk[:MAGICK_NUMBER],
vblk[:SEQUENCE_NUMBER],
vblk[:GROUP_NUMBER],
vblk[:RECORD_NUMBER],
vblk[:NUMBER_OF_RECORD],
vblk[:UPDATE_STATUS]);
printf("RECORD_TYPE_AND_FLAG : %04x %s\n", record_type_and_flag, VBLK_RECORD_TYPE[record_type]);
printf("DATA_LENGTH : %016x %20s\n", vblk[:DATA_LENGTH], ts(vblk[:DATA_LENGTH]));
vblk2 = read_items(fp, vblk_pos[0] + i * VBLK_SIZE, VBLK_DSTR[record_type])
vblk2.each{|name,val|
printf("%-20s : %s\n", name, val);
}
end
}
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment