resilience 0.0.2 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/bin/axe.rb +18 -0
  3. data/bin/fcomp.rb +15 -0
  4. data/bin/pex.rb +16 -0
  5. data/bin/rarser.rb +15 -0
  6. data/bin/reach.rb +16 -0
  7. data/bin/rex.rb +7 -66
  8. data/bin/rinfo.rb +19 -0
  9. data/lib/resilience.rb +5 -0
  10. data/lib/resilience/attribute.rb +21 -4
  11. data/lib/resilience/cli/all.rb +14 -0
  12. data/lib/resilience/cli/bin/axe.rb +34 -0
  13. data/lib/resilience/cli/bin/fcomp.rb +28 -0
  14. data/lib/resilience/cli/bin/pex.rb +43 -0
  15. data/lib/resilience/cli/bin/rarser.rb +72 -0
  16. data/lib/resilience/cli/bin/reach.rb +34 -0
  17. data/lib/resilience/cli/bin/rex.rb +33 -0
  18. data/lib/resilience/cli/bin/rinfo.rb +19 -0
  19. data/lib/resilience/cli/conf.rb +14 -0
  20. data/lib/resilience/cli/default.rb +17 -0
  21. data/lib/resilience/cli/disk.rb +40 -0
  22. data/lib/resilience/cli/file.rb +23 -0
  23. data/lib/resilience/cli/image.rb +45 -0
  24. data/lib/resilience/cli/metadata.rb +24 -0
  25. data/lib/resilience/cli/output.rb +84 -0
  26. data/lib/resilience/collections/dirs.rb +8 -0
  27. data/lib/resilience/collections/files.rb +41 -0
  28. data/lib/resilience/collections/pages.rb +16 -0
  29. data/lib/resilience/conf.rb +28 -0
  30. data/lib/resilience/constants.rb +31 -7
  31. data/lib/resilience/core_ext.rb +39 -0
  32. data/lib/resilience/fs_dir.rb +3 -0
  33. data/lib/resilience/fs_dir/dir_base.rb +16 -6
  34. data/lib/resilience/fs_dir/dir_entry.rb +33 -0
  35. data/lib/resilience/fs_dir/file_entry.rb +53 -0
  36. data/lib/resilience/image.rb +33 -0
  37. data/lib/resilience/mixins/on_image.rb +25 -0
  38. data/lib/resilience/page.rb +104 -0
  39. data/lib/resilience/tables.rb +1 -0
  40. data/lib/resilience/tables/boot.rb +89 -0
  41. data/lib/resilience/tables/object.rb +6 -2
  42. data/lib/resilience/tables/system.rb +5 -2
  43. data/lib/resilience/trees.rb +5 -0
  44. data/lib/resilience/trees/object_tree.rb +45 -0
  45. metadata +55 -15
  46. data/bin/cle.rb +0 -24
  47. data/bin/clum.py +0 -28
  48. data/bin/fs.rb +0 -21
  49. data/bin/ref.rb +0 -49
  50. data/bin/rels.rb +0 -269
  51. data/bin/resilience.rb +0 -351
@@ -2,5 +2,6 @@
2
2
  # ReFS Tables
3
3
  # Copyright (C) 2015 Red Hat Inc.
4
4
 
5
+ require 'resilience/tables/boot'
5
6
  require 'resilience/tables/object'
6
7
  require 'resilience/tables/system'
@@ -0,0 +1,89 @@
1
+ #!/usr/bin/ruby
2
+ # ReFS Boot Table
3
+ # Copyright (C) 2015 Red Hat Inc.
4
+
5
+ module Resilience
6
+ class BootTable
7
+ include OnImage
8
+
9
+ SECTOR_SIZE = 0x200
10
+
11
+ def refs_offsets
12
+ offsets = []
13
+ fs_off = fs_offsets
14
+ image.offset = 0
15
+ fs_off.each do |address|
16
+ image.seek address
17
+ sig = image.read(FS_SIGNATURE.size).unpack('C*')
18
+ offsets << address if sig == FS_SIGNATURE
19
+ end
20
+ offsets
21
+ end
22
+
23
+ def refs_offset
24
+ refs_offsets.first
25
+ end
26
+ end
27
+
28
+ class MBR < BootTable
29
+ SIG_ADDRESS = 0x1FE
30
+ SIG = [0x55, 0xAA]
31
+ PARTITION_ADDRESSES = [0x1C6, 0x1D6, 0x1E6, 0x1F6]
32
+
33
+ def detected?
34
+ image.seek SIG_ADDRESS
35
+ sig = image.read(2)
36
+ sig == SIG
37
+ end
38
+
39
+ def fs_offsets
40
+ offsets = []
41
+ PARTITION_ADDRESSES.each do |address|
42
+ image.seek address
43
+ address = image.read(4).unpack('V').first
44
+ offsets << address * SECTOR_SIZE unless address == 0
45
+ end
46
+ offsets
47
+ end
48
+
49
+ def gpt_offsets
50
+ offsets = []
51
+ fs_offsets.each do |address|
52
+ image.seek address
53
+ sig = image.read(GPT::SIG.size).unpack('C*')
54
+ offsets << address if sig == GPT::SIG
55
+ end
56
+ offsets
57
+ end
58
+
59
+ def gpt_offset
60
+ gpt_offsets.first
61
+ end
62
+ end # class MBR
63
+
64
+ class GPT < BootTable
65
+ NUM_ADDRESS = 0x50
66
+ PARTITIONS_START = 0x200 # from the current image offset, the start of gpt partition
67
+ PARTITION_SIZE = 0x80
68
+ OFFSET_ADDRESS = 0x20
69
+
70
+ SIG = [0x45, 0x46, 0x49, 0x20, 0x50, 0x41, 0x52, 0x54] # EFI PART
71
+
72
+ def num
73
+ image.seek NUM_ADDRESS
74
+ image.read(4).unpack('V').first
75
+ end
76
+
77
+ def fs_offsets
78
+ offsets = []
79
+ 0.upto(num-1) do |i|
80
+ image.seek PARTITIONS_START + i * PARTITION_SIZE
81
+ partition = image.read(PARTITION_SIZE).unpack('C*')
82
+ offset = partition[OFFSET_ADDRESS...OFFSET_ADDRESS+8].pack('C*').unpack('V').first
83
+ offsets << offset * SECTOR_SIZE
84
+ break if offset == 0
85
+ end
86
+ offsets
87
+ end
88
+ end # class GPT
89
+ end # module Resilience
@@ -18,11 +18,15 @@ module Resilience
18
18
  table
19
19
  end
20
20
 
21
- def parse_pages
21
+ # Depends on SystemTable extraction
22
+ def object_page_id
22
23
  # in the images I've seen this has always been the first entry
23
24
  # in the system table, though always has virtual page id = 2
24
25
  # which we could look for if this turns out not to be the case
25
- object_page_id = image.system_table.pages.first
26
+ image.system_table.pages.first
27
+ end
28
+
29
+ def parse_pages
26
30
  object_page_address = object_page_id * PAGE_SIZE
27
31
 
28
32
  # read number of objects from index header
@@ -18,8 +18,12 @@ module Resilience
18
18
  table
19
19
  end
20
20
 
21
+ def self.first_page_address
22
+ PAGES[:first] * PAGE_SIZE
23
+ end
24
+
21
25
  def parse_pages
22
- image.seek(FIRST_PAGE_ADDRESS + ADDRESSES[:system_table_page])
26
+ image.seek(self.class.first_page_address + ADDRESSES[:system_table_page])
23
27
  system_table_page = image.read(8).unpack('Q').first
24
28
  system_table_address = system_table_page * PAGE_SIZE
25
29
 
@@ -39,4 +43,3 @@ module Resilience
39
43
  end
40
44
  end # class SystemTable
41
45
  end # module Resilience
42
-
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/ruby
2
+ # ReFS Trees
3
+ # Copyright (C) 2015 Red Hat Inc.
4
+
5
+ require 'resilience/trees/object_tree'
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/ruby
2
+ # ReFS Object Tree
3
+ # Copyright (C) 2015 Red Hat Inc.
4
+
5
+ module Resilience
6
+ class ObjectTree
7
+ include OnImage
8
+
9
+ attr_accessor :map
10
+
11
+ def initialize
12
+ @map ||= {}
13
+ end
14
+
15
+ def self.parse
16
+ tree = new
17
+ tree.parse_entries
18
+ tree
19
+ end
20
+
21
+ # Depends on Image Pages extraction
22
+ def page
23
+ image.pages.newest_for PAGES[:object_table]
24
+ end
25
+
26
+ def parse_entries
27
+ page.attributes.each { |attr|
28
+ obj1 = obj1_from attr
29
+ obj2 = obj2_from attr
30
+ @map[obj1] ||= []
31
+ @map[obj1] << obj2
32
+ }
33
+ end
34
+
35
+ private
36
+
37
+ def obj1_from(attr)
38
+ attr.bytes[ADDRESSES[:object_tree_start1]..ADDRESSES[:object_tree_end1]]
39
+ end
40
+
41
+ def obj2_from(attr)
42
+ attr.bytes[ADDRESSES[:object_tree_start2]..ADDRESSES[:object_tree_end2]]
43
+ end
44
+ end # class ObjectTree
45
+ end # module Resilience
metadata CHANGED
@@ -1,50 +1,90 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: resilience
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mo Morsi
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-05-23 00:00:00.000000000 Z
12
- dependencies: []
11
+ date: 2015-07-06 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: colored
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
13
27
  description: Ruby ReFS utils
14
28
  email: mmorsi@redhat.com
15
29
  executables:
16
- - fs.rb
17
- - resilience.rb
30
+ - axe.rb
31
+ - rarser.rb
32
+ - reach.rb
33
+ - pex.rb
18
34
  - rex.rb
19
- - rels.rb
20
- - ref.rb
21
- - clum.py
22
- - cle.rb
35
+ - rinfo.rb
36
+ - fcomp.rb
23
37
  extensions: []
24
38
  extra_rdoc_files: []
25
39
  files:
26
40
  - README.md
27
- - bin/cle.rb
28
- - bin/clum.py
29
- - bin/fs.rb
30
- - bin/ref.rb
31
- - bin/rels.rb
32
- - bin/resilience.rb
41
+ - bin/axe.rb
42
+ - bin/fcomp.rb
43
+ - bin/pex.rb
44
+ - bin/rarser.rb
45
+ - bin/reach.rb
33
46
  - bin/rex.rb
47
+ - bin/rinfo.rb
34
48
  - lib/resilience.rb
35
49
  - lib/resilience/attribute.rb
50
+ - lib/resilience/cli/all.rb
51
+ - lib/resilience/cli/bin/axe.rb
52
+ - lib/resilience/cli/bin/fcomp.rb
53
+ - lib/resilience/cli/bin/pex.rb
54
+ - lib/resilience/cli/bin/rarser.rb
55
+ - lib/resilience/cli/bin/reach.rb
56
+ - lib/resilience/cli/bin/rex.rb
57
+ - lib/resilience/cli/bin/rinfo.rb
58
+ - lib/resilience/cli/conf.rb
59
+ - lib/resilience/cli/default.rb
60
+ - lib/resilience/cli/disk.rb
61
+ - lib/resilience/cli/file.rb
62
+ - lib/resilience/cli/image.rb
63
+ - lib/resilience/cli/metadata.rb
64
+ - lib/resilience/cli/output.rb
65
+ - lib/resilience/collections/dirs.rb
66
+ - lib/resilience/collections/files.rb
67
+ - lib/resilience/collections/pages.rb
68
+ - lib/resilience/conf.rb
36
69
  - lib/resilience/constants.rb
70
+ - lib/resilience/core_ext.rb
37
71
  - lib/resilience/dirs.rb
38
72
  - lib/resilience/dirs/root_dir.rb
39
73
  - lib/resilience/fs_dir.rb
40
74
  - lib/resilience/fs_dir/dir_base.rb
75
+ - lib/resilience/fs_dir/dir_entry.rb
76
+ - lib/resilience/fs_dir/file_entry.rb
41
77
  - lib/resilience/fs_dir/record.rb
42
78
  - lib/resilience/image.rb
43
79
  - lib/resilience/mixins.rb
44
80
  - lib/resilience/mixins/on_image.rb
81
+ - lib/resilience/page.rb
45
82
  - lib/resilience/tables.rb
83
+ - lib/resilience/tables/boot.rb
46
84
  - lib/resilience/tables/object.rb
47
85
  - lib/resilience/tables/system.rb
86
+ - lib/resilience/trees.rb
87
+ - lib/resilience/trees/object_tree.rb
48
88
  homepage: https://gist.github.com/movitto/866de4356f56a3b478ca
49
89
  licenses:
50
90
  - MIT
data/bin/cle.rb DELETED
@@ -1,24 +0,0 @@
1
- #!/usr/bin/ruby
2
- # ReFS 0x4000 Cluster Extractor
3
- # By mmorsi - 2014-07-14
4
-
5
- CLUSTERS = [0x1e, 0x20, 0x21, 0x22, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38,
6
- 0x2c0, 0x2c1, 0x2c2, 0x2c3, 0x2c4, 0x2c5, 0x2c6, 0x2c7, 0x2c8, 0x2cc, 0x2cd, 0x2ce, 0x2cf]
7
-
8
- DISK = 'flat-disk.vmdk'
9
- OUTDIR = 'clusters'
10
-
11
- REFS_VOLUME_OFFSET = 0x2100000
12
-
13
- inf = File.open(DISK, 'rb')
14
- CLUSTERS.each do |cluster|
15
- outf = File.open("#{OUTDIR}/#{cluster.to_s(16)}", 'wb')
16
-
17
- offset = REFS_VOLUME_OFFSET + cluster * 0x4000
18
- inf.seek(offset)
19
- contents = inf.read(0x4000)
20
- outf.write contents
21
- outf.close
22
- end
23
-
24
- inf.close
@@ -1,28 +0,0 @@
1
- # ReFS 0x4000 Cluster Usage Mapper
2
- # By Willi Ballenthin 2012-03-25
3
- # With modifications by mmorsi - 2014-07-14
4
- import sys, struct
5
-
6
- #REFS_VOLUME_OFFSET = 0x2010000
7
- REFS_VOLUME_OFFSET = 0x2100000
8
-
9
- def main(args):
10
- with open(args[1], "rb") as f:
11
- global REFS_VOLUME_OFFSET
12
- offset = REFS_VOLUME_OFFSET
13
- cluster = 0
14
- while True:
15
- f.seek(offset + cluster * 0x4000)
16
- buf = f.read(4)
17
- if not buf: break
18
- magic = struct.unpack("<I", buf)[0]
19
- if magic == cluster:
20
- print "Metadata cluster %s (%s)" % \
21
- (hex(cluster), hex(offset + cluster * 0x4000))
22
- elif magic != 0:
23
- print "Non-null cluster %s (%s)" % \
24
- (hex(cluster), hex(offset + cluster * 0x4000))
25
- cluster += 1
26
-
27
- if __name__ == '__main__':
28
- main(sys.argv)
data/bin/fs.rb DELETED
@@ -1,21 +0,0 @@
1
- #!/usr/bin/ruby
2
- # ReFS file searcher
3
- # By mmorsi - 2014-09-03
4
-
5
- DISK = 'flat-disk.vmdk'
6
- OFFSET = 0x2100000
7
-
8
- SEQUENCE_LENGTH = 8
9
- SEQUENCE = 0xe010002800000038 # inverted due to endian ordering
10
-
11
- inf = File.open(DISK, 'rb')
12
- inf.seek(OFFSET)
13
-
14
- while check = inf.read(SEQUENCE_LENGTH)
15
- check = check.unpack('Q').first
16
- if check == SEQUENCE
17
- puts 'File at: 0x' + inf.pos.to_s(16) + ' cluster ' + ((inf.pos - OFFSET) / 0x4000).to_s(16)
18
- end
19
- end
20
-
21
- inf.close
data/bin/ref.rb DELETED
@@ -1,49 +0,0 @@
1
- #!/usr/bin/ruby
2
- # ReFS reference analyzer
3
- # By mmorsi - 2014-09-03
4
-
5
- require 'graphviz'
6
-
7
- CLUSTERS = [0x1e, 0x2e, 0x37, 0x38,
8
- 0x2c0, 0x2c1, 0x2c2, 0x2c3, 0x2c4, 0x2c5, 0x2c6, 0x2c7, 0x2c8, 0x2cc, 0x2cd, 0x2ce, 0x2cf]
9
-
10
- DISK = 'flat-disk.vmdk'
11
- OFFSET = 0x2100000
12
-
13
- inf = File.open(DISK, 'rb')
14
-
15
- refs = {}
16
-
17
- CLUSTERS.each do |cluster|
18
- offset = OFFSET + cluster * 0x4000
19
- inf.seek(offset + 2) # skip first 2 bytes containing cluster #
20
- while inf.pos < offset + 0x4000
21
- checkb = inf.read(2).unpack('S').first
22
- CLUSTERS.each do |checkc|
23
- if checkb == checkc
24
- refs[cluster] ||= {}
25
- refs[cluster][checkc] ||= 0
26
- refs[cluster][checkc] += 1
27
- end
28
- end
29
- end
30
- end
31
-
32
- inf.close
33
-
34
- g = GraphViz.new( :G, :type => :digraph )
35
-
36
- refs.keys.each { |cluster|
37
- g.add_nodes cluster.to_s(16)
38
- }
39
-
40
- refs.keys.each { |cluster|
41
- puts "cluster #{cluster.to_s(16)} references: "
42
- refs[cluster].keys.each { |ref|
43
- puts ref.to_s(16) + " (#{refs[cluster][ref]})"
44
- #g.add_edges(cluster.to_s(16), ref.to_s(16))
45
- g.add_edges(ref.to_s(16), cluster.to_s(16))
46
- }
47
- }
48
-
49
- g.output :png => 'fan-out.png'
@@ -1,269 +0,0 @@
1
- #!/usr/bin/ruby
2
- # ReFS File Lister
3
- # Copyright (C) 2014 Red Hat Inc.
4
-
5
- require 'optparse'
6
- require 'colored'
7
-
8
- FIRST_PAGE_ID = 0x1e
9
- PAGE_SIZE = 0x4000
10
- FIRST_PAGE_ADDRESS = FIRST_PAGE_ID * PAGE_SIZE
11
-
12
- ROOT_DIR_ID = [0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0]
13
- DIR_ENTRY = 0x20030
14
- FILE_ENTRY = 0x10030
15
-
16
- DIR_TREE = 0x301
17
- DIR_LIST = 0x200
18
- #DIR_BRANCH = 0x000 ?
19
-
20
- ADDRESSES = {
21
- # page
22
- :virtual_page_number => 0x18,
23
- :first_attr => 0x30,
24
-
25
- # page 0x1e
26
- :system_table_page => 0xA0,
27
-
28
- # system table
29
- :system_pages => 0x58,
30
-
31
- # generic table
32
- :num_objects => 0x20, # referenced from start of first attr
33
-
34
- :table_length => 0x04 # referenced from start of table header
35
- }
36
-
37
- def read_attribute(image)
38
- pos = image.pos
39
- attr_len = image.read(4).unpack('L').first
40
- return nil if attr_len == 0
41
- image.seek pos
42
-
43
- image.read(attr_len)
44
- end
45
-
46
- def record_boundries(attribute)
47
- header = attribute.unpack('S*')
48
- key_offset = header[2]
49
- key_length = header[3]
50
- value_offset = header[5]
51
- value_length = header[6]
52
- [key_offset, key_length, value_offset, value_length]
53
- end
54
-
55
- def record_flags(attribute)
56
- attribute.unpack('S*')[4]
57
- end
58
-
59
- def record_key(attribute)
60
- ko, kl, vo, vl = record_boundries(attribute)
61
- attribute.unpack('C*')[ko...ko+kl].pack('C*')
62
- end
63
-
64
- def record_value(attribute)
65
- ko, kl, vo, vl = record_boundries(attribute)
66
- attribute.unpack('C*')[vo..-1].pack('C*')
67
- end
68
-
69
- def filter_dir_record(dir_entry, opts)
70
- image = opts[:image]
71
-
72
- # '4' seems to indicate a historical record or similar,
73
- # records w/ flags '0' or '8' are what we want
74
- record_flags(dir_entry)== 4 ? filter_dir_record(read_attribute(opts[:image]), opts) : dir_entry
75
- end
76
-
77
- def parse_dir_branch(node, prefix, opts)
78
- key = record_key(node)
79
- value = record_value(node)
80
- flags = record_flags(node)
81
-
82
- value_dwords = value.unpack('L*')
83
- value_qwords = value.unpack('Q*')
84
-
85
- page_id = value_dwords[0]
86
- page_address = page_id * PAGE_SIZE
87
- checksum = value_qwords[2]
88
- parse_dir_page page_address, prefix, opts unless checksum == 0 || flags == 4
89
- end
90
-
91
- def parse_dir_record(dir_entry, prefix, opts)
92
- key = record_key(dir_entry)
93
- value = record_value(dir_entry)
94
-
95
- key_bytes = key.unpack('C*')
96
- key_dwords = key.unpack('L*')
97
- entry_type = key_dwords.first
98
- if entry_type == DIR_ENTRY
99
- dir_name = key_bytes[4..-1].pack('L*')
100
- opts[:dirs] << "#{prefix}\\#{dir_name}"
101
-
102
- dir_obj = value.unpack('C*')[0...8]
103
- dir_obj = [0, 0, 0, 0, 0, 0, 0, 0].concat(dir_obj)
104
- parse_dir_obj(dir_obj, "#{prefix}\\#{dir_name}", opts)
105
-
106
- elsif entry_type == FILE_ENTRY
107
- file_name = key_bytes[4..-1].pack('L*')
108
- opts[:files] << "#{prefix}\\#{file_name}"
109
- end
110
- end
111
-
112
- def parse_dir_obj(object_id, prefix, opts)
113
- image = opts[:image]
114
- object_pages = opts[:object_pages]
115
- opts[:dirs] ||= []
116
- opts[:files] ||= []
117
-
118
- page_id = object_pages[object_id]
119
- page_address = page_id * PAGE_SIZE
120
- parse_dir_page page_address, prefix, opts
121
- end
122
-
123
- def parse_dir_page(page_address, prefix, opts)
124
- image = opts[:image]
125
- image_start = opts[:offset]
126
-
127
- # skip container/placeholder attribute
128
- image.seek(image_start + page_address + ADDRESSES[:first_attr])
129
- read_attribute(image)
130
-
131
- # start of table attr, pull out table length, type
132
- table_header_attr = read_attribute(image)
133
- table_header_dwords = table_header_attr.unpack("L*")
134
- header_len = table_header_dwords[0]
135
- table_len = table_header_dwords[1]
136
- remaining_len = table_len - header_len
137
- table_type = table_header_dwords[3]
138
-
139
- until remaining_len == 0
140
- orig_pos = image.pos
141
- record = read_attribute(image)
142
-
143
- # need to keep track of position locally as we
144
- # recursively call parse_dir via helpers
145
- pos = image.pos
146
-
147
- if table_type == DIR_TREE
148
- parse_dir_branch record, prefix, opts
149
- else #if table_type == DIR_LIST
150
- record = filter_dir_record(record, opts)
151
- pos = image.pos
152
- parse_dir_record record, prefix, opts
153
-
154
- end
155
-
156
- image.seek pos
157
- remaining_len -= (image.pos - orig_pos)
158
- end
159
- end
160
-
161
- def parse_object_table(opts)
162
- image = opts[:image]
163
- image_start = opts[:offset]
164
- opts[:object_pages] ||= {}
165
-
166
- # in the images I've seen this has always been the first entry
167
- # in the system table, though always has virtual page id = 2
168
- # which we could look for if this turns out not to be the case
169
- object_page_id = opts[:system_pages].first
170
- object_page_address = object_page_id * PAGE_SIZE
171
-
172
- # read number of objects from index header
173
- image.seek(image_start + object_page_address + ADDRESSES[:first_attr])
174
- first_attr = read_attribute(image)
175
- num_objects = first_attr.unpack('L*')[ADDRESSES[:num_objects]/4]
176
-
177
- # start of table attr, skip for now
178
- read_attribute(image)
179
-
180
- 0.upto(num_objects-1) do
181
- object_record = read_attribute(image)
182
- object_id = record_key(object_record).unpack('C*')
183
-
184
- # here object page is first qword of record value
185
- object_page = record_value(object_record).unpack('Q*').first
186
- opts[:object_pages][object_id] = object_page
187
- end
188
- end
189
-
190
- def parse_system_table(opts)
191
- image = opts[:image]
192
- image_start = opts[:offset]
193
- opts[:system_pages] ||= []
194
-
195
- image.seek(image_start + FIRST_PAGE_ADDRESS + ADDRESSES[:system_table_page])
196
- system_table_page = image.read(8).unpack('Q').first
197
- system_table_address = system_table_page * PAGE_SIZE
198
-
199
- image.seek(image_start + system_table_address + ADDRESSES[:system_pages])
200
- num_system_pages = image.read(4).unpack('L').first
201
-
202
- 0.upto(num_system_pages-1) do
203
- system_page_offset = image.read(4).unpack('L').first
204
- pos = image.pos
205
-
206
- image.seek(image_start + system_table_address + system_page_offset)
207
- system_page = image.read(8).unpack('Q').first
208
- opts[:system_pages] << system_page
209
-
210
- image.seek(pos)
211
- end
212
- end
213
-
214
- def print_results(opts)
215
- puts "Dirs found:".bold
216
- opts[:dirs].each { |dir|
217
- puts "#{dir}".blue
218
- }
219
-
220
- puts
221
- puts "Files found:".bold
222
- opts[:files].sort.each { |file|
223
- puts "#{file}".red
224
- }
225
- end
226
-
227
- def main(opts = {})
228
- image = File.open(opts[:image], 'rb')
229
- opts[:image] = image
230
-
231
- parse_system_table(opts)
232
- parse_object_table(opts)
233
- parse_dir_obj(ROOT_DIR_ID, '', opts)
234
- print_results(opts)
235
- end
236
-
237
- def parse_cli(cli)
238
- opts = {}
239
- parser = OptionParser.new do |popts|
240
- popts.on("-h", "--help", "Print help message") do
241
- puts parser
242
- exit
243
- end
244
-
245
- popts.on("-i", "--image path", "Path to the disk image to parse") do |path|
246
- opts[:image] = path
247
- end
248
-
249
- popts.on("-o", "--offset bytes", "Start of volume with ReFS filesystem") do |offset|
250
- opts[:offset] = offset.to_i
251
- end
252
- end
253
-
254
- begin
255
- parser.parse!(cli)
256
- rescue OptionParser::InvalidOption
257
- puts parser
258
- exit
259
- end
260
-
261
- if !opts[:image] || !opts[:offset]
262
- puts "--image and --offset params are needed at a minimum"
263
- exit 1
264
- end
265
-
266
- opts
267
- end
268
-
269
- main parse_cli(ARGV) if __FILE__ == $0