resilience 0.0.2 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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