Last active
June 10, 2016 12:17
-
-
Save kou1okada/17f14f48f7524867bedef090362fab47 to your computer and use it in GitHub Desktop.
NTFS Dynamic Disk Information tool
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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