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