rex 2.0.9 → 2.0.10

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 (48) hide show
  1. checksums.yaml +4 -4
  2. data/lib/rex/exploitation/cmdstager/bourne.rb +14 -8
  3. data/lib/rex/exploitation/cmdstager/echo.rb +3 -3
  4. data/lib/rex/exploitation/js/memory.rb +1 -1
  5. data/lib/rex/java/serialization/model/contents.rb +1 -1
  6. data/lib/rex/mime/message.rb +1 -1
  7. data/lib/rex/parser/acunetix_nokogiri.rb +2 -0
  8. data/lib/rex/parser/appscan_nokogiri.rb +1 -1
  9. data/lib/rex/parser/burp_issue_nokogiri.rb +139 -0
  10. data/lib/rex/parser/burp_session_nokogiri.rb +1 -1
  11. data/lib/rex/parser/fs/bitlocker.rb +233 -0
  12. data/lib/rex/parser/fusionvm_nokogiri.rb +2 -2
  13. data/lib/rex/parser/ini.rb +1 -8
  14. data/lib/rex/parser/nokogiri_doc_mixin.rb +5 -0
  15. data/lib/rex/payloads/meterpreter/config.rb +23 -4
  16. data/lib/rex/post/meterpreter/channel.rb +8 -3
  17. data/lib/rex/post/meterpreter/client.rb +1 -0
  18. data/lib/rex/post/meterpreter/client_core.rb +2 -2
  19. data/lib/rex/post/meterpreter/extensions/android/android.rb +86 -1
  20. data/lib/rex/post/meterpreter/extensions/android/tlv.rb +29 -0
  21. data/lib/rex/post/meterpreter/extensions/extapi/wmi/wmi.rb +1 -1
  22. data/lib/rex/post/meterpreter/extensions/stdapi/net/socket_subsystem/tcp_server_channel.rb +75 -89
  23. data/lib/rex/post/meterpreter/extensions/stdapi/sys/event_log.rb +8 -2
  24. data/lib/rex/post/meterpreter/extensions/stdapi/sys/process.rb +10 -5
  25. data/lib/rex/post/meterpreter/extensions/stdapi/sys/registry_subsystem/registry_key.rb +7 -2
  26. data/lib/rex/post/meterpreter/extensions/stdapi/sys/registry_subsystem/remote_registry_key.rb +10 -5
  27. data/lib/rex/post/meterpreter/extensions/stdapi/sys/thread.rb +8 -2
  28. data/lib/rex/post/meterpreter/extensions/stdapi/webcam/webcam.rb +1 -1
  29. data/lib/rex/post/meterpreter/packet.rb +38 -0
  30. data/lib/rex/post/meterpreter/packet_dispatcher.rb +101 -108
  31. data/lib/rex/post/meterpreter/packet_parser.rb +14 -6
  32. data/lib/rex/post/meterpreter/packet_response_waiter.rb +42 -21
  33. data/lib/rex/post/meterpreter/ui/console/command_dispatcher/android.rb +54 -4
  34. data/lib/rex/post/meterpreter/ui/console/command_dispatcher/core.rb +39 -13
  35. data/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi/fs.rb +8 -0
  36. data/lib/rex/proto/adb.rb +7 -0
  37. data/lib/rex/proto/adb/client.rb +39 -0
  38. data/lib/rex/proto/adb/message.rb +164 -0
  39. data/lib/rex/proto/dcerpc/svcctl/packet.rb +9 -9
  40. data/lib/rex/proto/http/client_request.rb +2 -1
  41. data/lib/rex/proto/http/response.rb +1 -1
  42. data/lib/rex/proto/kademlia/bootstrap_response.rb +2 -2
  43. data/lib/rex/proto/ntp/modes.rb +17 -0
  44. data/lib/rex/text.rb +12 -0
  45. data/lib/rex/zip/blocks.rb +1 -1
  46. data/lib/rex/zip/entry.rb +1 -1
  47. data/rex.gemspec +28 -1
  48. metadata +106 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5a35ae088f817815b2d3cd56ad4f298a1dc36a73
4
- data.tar.gz: f486b6412dd555646eb747c4b65a4b01dd623f38
3
+ metadata.gz: 905f84863b058a5c12986cb77bd82bfeb9e5948f
4
+ data.tar.gz: c13bf88f1ffda5f7b730a29d6131c80e6a3a106c
5
5
  SHA512:
6
- metadata.gz: 96d9eed7f6fa713ae93f52a629113b9f5cdb41aed48db814731612bf2dbe74a0c657f83905640e188c1dcb622b3781125737e1faf1b06ab1eb29d15e99941acc
7
- data.tar.gz: a7092a603aeab1a5291f522438f8a94a56e28a6f6134d1024c1b8fce85956e0432e818bf83fcee1710bdd2aeeb0c1bf2b297e7f40154f2a7544f20fb043d69f0
6
+ metadata.gz: 2e2b6a00d84ebf9e551c906d131695a01842c0249c1bb7fb2c7882f41bfd1e5d8da535e092d03a2862c44a1a82355fa0d0efc55d70c680aadd8e38e0c8243561
7
+ data.tar.gz: 6702bf1c601ca43627ecf06303ebe80b507b7b178081b872f353956806949961eda4c3c21ed01f20400db12c7ac791ad13cab486aea80c03be3dd93113423bc3
@@ -12,14 +12,20 @@ class CmdStagerBourne < CmdStagerBase
12
12
  def initialize(exe)
13
13
  super
14
14
 
15
- @var_encoded = Rex::Text.rand_text_alpha(5)
15
+ @var_encoded = Rex::Text.rand_text_alpha(5) + '.b64'
16
16
  @var_decoded = Rex::Text.rand_text_alpha(5)
17
17
  end
18
18
 
19
19
  def generate(opts = {})
20
20
  opts[:temp] = opts[:temp] || '/tmp/'
21
+ opts[:temp] = opts[:temp].empty?? opts[:temp] : opts[:temp] + '/'
22
+ opts[:temp] = opts[:temp].gsub(/\/{2,}/, '/')
21
23
  opts[:temp] = opts[:temp].gsub(/'/, "\\\\'")
22
24
  opts[:temp] = opts[:temp].gsub(/ /, "\\ ")
25
+ if (opts[:file])
26
+ @var_encoded = opts[:file] + '.b64'
27
+ @var_decoded = opts[:file]
28
+ end
23
29
  super
24
30
  end
25
31
 
@@ -29,7 +35,7 @@ class CmdStagerBourne < CmdStagerBase
29
35
  def generate_cmds(opts)
30
36
  # Set the start/end of the commands here (vs initialize) so we have @tempdir
31
37
  @cmd_start = "echo -n "
32
- @cmd_end = ">>#{@tempdir}#{@var_encoded}.b64"
38
+ @cmd_end = ">>'#{@tempdir}#{@var_encoded}'"
33
39
  xtra_len = @cmd_start.length + @cmd_end.length + 1
34
40
  opts.merge!({ :extra => xtra_len })
35
41
  super
@@ -78,27 +84,27 @@ class CmdStagerBourne < CmdStagerBase
78
84
  decoder_cmd << "(which #{binary} >&2 && #{cmd})"
79
85
  end
80
86
  decoder_cmd = decoder_cmd.join(" || ")
81
- decoder_cmd = "(" << decoder_cmd << ") 2> /dev/null > #{@tempdir}#{@var_decoded}.bin < #{@tempdir}#{@var_encoded}.b64"
87
+ decoder_cmd = "(" << decoder_cmd << ") 2> /dev/null > '#{@tempdir}#{@var_decoded}' < '#{@tempdir}#{@var_encoded}'"
82
88
  [ decoder_cmd ]
83
89
  end
84
90
 
85
91
  def compress_commands(cmds, opts)
86
92
  # Make it all happen
87
- cmds << "chmod +x #{@tempdir}#{@var_decoded}.bin"
93
+ cmds << "chmod +x '#{@tempdir}#{@var_decoded}'"
88
94
  # Background the process, allowing the cleanup code to continue and delete the data
89
95
  # while allowing the original shell to continue to function since it isn't waiting
90
96
  # on the payload to exit. The 'sleep' is required as '&' is a command terminator
91
97
  # and having & and the cmds delimiter ';' next to each other is invalid.
92
98
  if opts[:background]
93
- cmds << "#{@tempdir}#{@var_decoded}.bin & sleep 2"
99
+ cmds << "'#{@tempdir}#{@var_decoded}' & sleep 2"
94
100
  else
95
- cmds << "#{@tempdir}#{@var_decoded}.bin"
101
+ cmds << "'#{@tempdir}#{@var_decoded}'"
96
102
  end
97
103
 
98
104
  # Clean up after unless requested not to..
99
105
  if (not opts[:nodelete])
100
- cmds << "rm -f #{@tempdir}#{@var_decoded}.bin"
101
- cmds << "rm -f #{@tempdir}#{@var_encoded}.b64"
106
+ cmds << "rm -f '#{@tempdir}#{@var_decoded}'"
107
+ cmds << "rm -f '#{@tempdir}#{@var_encoded}'"
102
108
  end
103
109
 
104
110
  super
@@ -35,7 +35,7 @@ class CmdStagerEcho < CmdStagerBase
35
35
  end
36
36
 
37
37
  # by default use the 'hex' encoding
38
- opts[:enc_format] = opts[:enc_format] || 'hex'
38
+ opts[:enc_format] = opts[:enc_format].nil? ? 'hex' : opts[:enc_format].to_s
39
39
 
40
40
  unless ENCODINGS.keys.include?(opts[:enc_format])
41
41
  raise RuntimeError, "CmdStagerEcho - Invalid Encoding Option: #{opts[:enc_format]}"
@@ -58,7 +58,7 @@ class CmdStagerEcho < CmdStagerBase
58
58
  xtra_len = @cmd_start.length + @cmd_end.length
59
59
  opts.merge!({ :extra => xtra_len })
60
60
 
61
- @prefix = ENCODINGS[opts[:enc_format]]
61
+ @prefix = opts[:prefix] || ENCODINGS[opts[:enc_format]]
62
62
  min_part_size = 5 # for both encodings
63
63
 
64
64
  if (opts[:linemax] - opts[:extra]) < min_part_size
@@ -108,7 +108,7 @@ class CmdStagerEcho < CmdStagerBase
108
108
  # Make it all happen
109
109
  cmds << "chmod 777 #{@tempdir}#{@var_elf}"
110
110
  #cmds << "chmod +x #{@tempdir}#{@var_elf}"
111
- cmds << "#{@tempdir}#{@var_elf}"
111
+ cmds << "#{@tempdir}#{@var_elf}#{' & echo' if opts[:background]}"
112
112
 
113
113
  # Clean up after unless requested not to..
114
114
  unless opts[:nodelete]
@@ -27,7 +27,7 @@ class Memory
27
27
  def self.heaplib2(custom_js='', opts={})
28
28
  js = ::File.read(::File.join(Msf::Config.data_directory, "js", "memory", "heaplib2.js"))
29
29
 
30
- unless custom_js.blank?
30
+ unless custom_js.to_s.empty?
31
31
  js << custom_js
32
32
  end
33
33
 
@@ -88,7 +88,7 @@ module Rex
88
88
  when NewClassDesc
89
89
  encoded << [TC_CLASSDESC].pack('C')
90
90
  when ProxyClassDesc
91
- content = [TC_PROXYCLASSDESC].pack('C')
91
+ encoded << [TC_PROXYCLASSDESC].pack('C')
92
92
  when NullReference
93
93
  encoded << [TC_NULL].pack('C')
94
94
  when Reset
@@ -126,7 +126,7 @@ class Message
126
126
  header_string = self.header.to_s
127
127
 
128
128
  msg = header_string.empty? ? '' : force_crlf(self.header.to_s + "\r\n")
129
- msg << force_crlf(self.content + "\r\n") unless self.content.blank?
129
+ msg << force_crlf(self.content + "\r\n") unless self.content.to_s.empty?
130
130
 
131
131
  self.parts.each do |part|
132
132
  msg << force_crlf("--" + self.bound + "\r\n")
@@ -59,6 +59,8 @@ module Rex
59
59
  @text = nil
60
60
  when "StartURL" # Populates @state[:starturl_uri], we use this a lot
61
61
  @state[:has_text] = false
62
+ # StartURL does not always include the scheme
63
+ @text.prepend("http://") unless URI.parse(@text).scheme
62
64
  collect_host
63
65
  collect_service
64
66
  @text = nil
@@ -195,7 +195,7 @@ module Rex
195
195
  res_header = Rex::Proto::Http::Packet::Header.new
196
196
  req_header.from_s request_headers.lstrip
197
197
  res_header.from_s response_headers.lstrip
198
- if response_body.blank?
198
+ if response_body.to_s.empty?
199
199
  response_body = ''
200
200
  end
201
201
  @state[:request_headers] = req_header
@@ -0,0 +1,139 @@
1
+ # -*- coding: binary -*-
2
+ require "rex/parser/nokogiri_doc_mixin"
3
+ require 'uri'
4
+
5
+ module Rex
6
+ module Parser
7
+
8
+ # If Nokogiri is available, define Burp Issue document class.
9
+ load_nokogiri && class BurpIssueDocument < Nokogiri::XML::SAX::Document
10
+
11
+ include NokogiriDocMixin
12
+
13
+ def start_element(name=nil,attrs=[])
14
+ attrs = normalize_attrs(attrs)
15
+ block = @block
16
+ @state[:current_tag][name] = true
17
+ case name
18
+ when "host", "name", "info", "issueDetail", "references"
19
+ @state[:has_text] = true
20
+ end
21
+ end
22
+
23
+ def end_element(name=nil)
24
+ block = @block
25
+ case name
26
+ when "issue"
27
+ report_web_host_info
28
+ report_web_service_info
29
+ report_vuln
30
+ # Reset the state once we close a host
31
+ @state = @state.select {|k| [:current_tag].include? k}
32
+ when "host"
33
+ @state[:has_text] = false
34
+ collect_host_info
35
+ @text = nil
36
+ when "name"
37
+ @state[:has_text] = false
38
+ collect_name
39
+ @text = nil
40
+ when "issueDetail"
41
+ @state[:has_text] = false
42
+ collect_issue_detail
43
+ @text = nil
44
+ when "references"
45
+ @state[:has_text] = false
46
+ collect_references
47
+ @text = nil
48
+ end
49
+ @state[:current_tag].delete name
50
+ end
51
+
52
+ def collect_host_info
53
+ return unless in_issue
54
+ return unless has_text
55
+ uri = URI(@text)
56
+
57
+ @state[:host] = uri.host
58
+ @state[:service_name] = uri.scheme
59
+ @state[:proto] = "tcp"
60
+
61
+ case @state[:service_name]
62
+ when "http"
63
+ @state[:port] = 80
64
+ when "https"
65
+ @state[:port] = 443
66
+ end
67
+ end
68
+
69
+ def collect_name
70
+ return unless in_issue
71
+ return unless has_text
72
+ @state[:vuln_name] = @text
73
+ end
74
+
75
+ def collect_issue_detail
76
+ return unless in_issue
77
+ return unless has_text
78
+ @state[:issue_detail] = @text
79
+ end
80
+
81
+ def collect_references
82
+ return unless in_issue
83
+ return unless has_text
84
+ uri = @text.match('href=[\'"]?([^\'" >]+)')[1]
85
+ @state[:refs] = ["URI-#{uri}"]
86
+ end
87
+
88
+ def report_web_host_info
89
+ return unless @state[:host]
90
+ address = Rex::Socket.resolv_to_dotted(@state[:host]) rescue nil
91
+ host_info = {:workspace => @args[:wspace]}
92
+ host_info[:address] = address
93
+ host_info[:name] = @state[:host]
94
+ db_report(:host, host_info)
95
+ end
96
+
97
+ def report_web_service_info
98
+ return unless @state[:host]
99
+ return unless @state[:port]
100
+ return unless @state[:proto]
101
+ return unless @state[:service_name]
102
+ service_info = {}
103
+ service_info[:host] = @state[:host]
104
+ service_info[:port] = @state[:port]
105
+ service_info[:proto] = @state[:proto]
106
+ service_info[:name] = @state[:service_name]
107
+ @state[:service_object] = db_report(:service, service_info)
108
+ end
109
+
110
+ def report_vuln
111
+ return unless @state[:service_object]
112
+ return unless @state[:vuln_name]
113
+ return unless @state[:issue_detail]
114
+ return unless @state[:refs]
115
+ vuln_info = {}
116
+ vuln_info[:service_id] = @state[:service_object].id
117
+ vuln_info[:host] = @state[:host]
118
+ vuln_info[:name] = @state[:vuln_name]
119
+ vuln_info[:info] = @state[:issue_detail]
120
+ vuln_info[:refs] = @state[:refs]
121
+ @state[:vuln_object] = db_report(:vuln, vuln_info)
122
+ end
123
+
124
+ def in_issue
125
+ return false unless in_tag("issue")
126
+ return false unless in_tag("issues")
127
+ return true
128
+ end
129
+
130
+ def has_text
131
+ return false unless @text
132
+ return false if @text.strip.empty?
133
+ @text = @text.strip
134
+ end
135
+ end
136
+
137
+ end
138
+ end
139
+
@@ -157,7 +157,7 @@ module Rex
157
157
  host_info = {:workspace => @args[:wspace]}
158
158
  host_info[:address] = @state[:web_site].service.host.address
159
159
  host_info[:name] = @state[:uri].host
160
- report_db(:host, host_info)
160
+ db_report(:host, host_info)
161
161
  end
162
162
 
163
163
  def report_web_service_info
@@ -0,0 +1,233 @@
1
+ # -*- coding: binary -*-
2
+ ##
3
+ # This module requires Metasploit: http://metasploit.com/download
4
+ # Current source: https://github.com/rapid7/metasploit-framework
5
+ ##
6
+ require 'openssl/ccm'
7
+ require 'metasm'
8
+ module Rex
9
+ module Parser
10
+ ###
11
+ #
12
+ # This class parses the content of a Bitlocker partition file.
13
+ # Author : Danil Bazin <danil.bazin[at]hsc.fr> @danilbaz
14
+ #
15
+ ###
16
+ class BITLOCKER
17
+ BLOCK_HEADER_SIZE = 64
18
+ METADATA_HEADER_SIZE = 48
19
+
20
+ ENTRY_TYPE_NONE = 0x0000
21
+ ENTRY_TYPE_VMK = 0x0002
22
+ ENTRY_TYPE_FVEK = 0x0003
23
+ ENTRY_TYPE_STARTUP_KEY = 0x0006
24
+ ENTRY_TYPE_DESC = 0x0007
25
+ ENTRY_TYPE_HEADER = 0x000f
26
+
27
+ VALUE_TYPE_ERASED = 0x0000
28
+ VALUE_TYPE_KEY = 0x0001
29
+ VALUE_TYPE_STRING = 0x0002
30
+ VALUE_TYPE_STRETCH_KEY = 0x0003
31
+ VALUE_TYPE_ENCRYPTED_KEY = 0x0005
32
+ VALUE_TYPE_TPM = 0x0006
33
+ VALUE_TYPE_VALIDATION = 0x0007
34
+ VALUE_TYPE_VMK = 0x0008
35
+ VALUE_TYPE_EXTERNAL_KEY = 0x0009
36
+ VALUE_TYPE_UPDATE = 0x000a
37
+ VALUE_TYPE_ERROR = 0x000b
38
+
39
+ PROTECTION_TPM = 0x0100
40
+ PROTECTION_CLEAR_KEY = 0x0000
41
+ PROTECTION_STARTUP_KEY = 0x0200
42
+ PROTECTION_RECOVERY_PASSWORD = 0x0800
43
+ PROTECTION_PASSWORD = 0x2000
44
+
45
+ def initialize(file_handler)
46
+ @file_handler = file_handler
47
+ volume_header = @file_handler.read(512)
48
+ @fs_sign = volume_header[3, 8]
49
+ unless @fs_sign == '-FVE-FS-'
50
+ fail ArgumentError, 'File system signature does not match Bitlocker :
51
+ #@fs_sign}, bitlocker not used', caller
52
+ end
53
+ @fve_offset = volume_header[176, 8].unpack('Q')[0]
54
+
55
+ @file_handler.seek(@fve_offset)
56
+ @fve_raw = @file_handler.read(4096)
57
+ @encryption_methods = @fve_raw[BLOCK_HEADER_SIZE + 36, 4].unpack('V')[0]
58
+ size = @fve_raw[BLOCK_HEADER_SIZE, 4].unpack('V')[0] -
59
+ METADATA_HEADER_SIZE
60
+ @metadata_entries = @fve_raw[BLOCK_HEADER_SIZE + METADATA_HEADER_SIZE,
61
+ size]
62
+ @version = @fve_raw[BLOCK_HEADER_SIZE + 4]
63
+ @fve_metadata_entries = fve_entries(@metadata_entries)
64
+ @vmk_entries_hash = vmk_entries
65
+ end
66
+
67
+ # Extract FVEK and prefix it with the encryption methods integer on
68
+ # 2 bytes
69
+ def fvek_from_recovery_password_dislocker(recoverykey)
70
+ [@encryption_methods].pack('v') +
71
+ fvek_from_recovery_password(recoverykey)
72
+ end
73
+
74
+ # stretch recovery key with all stretch key and try to decrypt all VMK
75
+ # encrypted with a recovery key
76
+ def vmk_from_recovery_password(recoverykey)
77
+ recovery_keys_stretched = recovery_key_transformation(recoverykey)
78
+ vmk_encrypted_in_recovery_password_list = @vmk_entries_hash[
79
+ PROTECTION_RECOVERY_PASSWORD]
80
+ vmk_recovery_password = ''
81
+ vmk_encrypted_in_recovery_password_list.each do |vmk|
82
+ vmk_encrypted = vmk[ENTRY_TYPE_NONE][VALUE_TYPE_ENCRYPTED_KEY][0]
83
+ recovery_keys_stretched.each do |recovery_key|
84
+ vmk_recovery_password = decrypt_aes_ccm_key(
85
+ vmk_encrypted, recovery_key)
86
+ break if vmk_recovery_password != ''
87
+ end
88
+ break if vmk_recovery_password != ''
89
+ end
90
+ if vmk_recovery_password == ''
91
+ fail ArgumentError, 'Wrong decryption, bad recovery key?'
92
+ end
93
+ vmk_recovery_password
94
+ end
95
+
96
+ # Extract FVEK using the provided recovery key
97
+ def fvek_from_recovery_password(recoverykey)
98
+ vmk_recovery_password = vmk_from_recovery_password(recoverykey)
99
+ fvek_encrypted = fvek_entries
100
+ fvek = decrypt_aes_ccm_key(fvek_encrypted, vmk_recovery_password)
101
+ fvek
102
+ end
103
+
104
+ def decrypt_aes_ccm_key(fve_entry, key)
105
+ nonce = fve_entry[0, 12]
106
+ mac = fve_entry[12, 16]
107
+ encrypted_data = fve_entry[28..-1]
108
+ ccm = OpenSSL::CCM.new('AES', key, 16)
109
+ decrypted_data = ccm.decrypt(encrypted_data + mac, nonce)
110
+ decrypted_data[12..-1]
111
+ end
112
+
113
+ # Parse the metadata_entries and return a hashmap using the
114
+ # following format:
115
+ # {metadata_entry_type => {metadata_value_type => [fve_entry,...]}}
116
+ def fve_entries(metadata_entries)
117
+ offset_entry = 0
118
+ entry_size = metadata_entries[0, 2].unpack('v')[0]
119
+ result = Hash.new({})
120
+ while entry_size != 0
121
+ metadata_entry_type = metadata_entries[
122
+ offset_entry + 2, 2].unpack('v')[0]
123
+ metadata_value_type = metadata_entries[
124
+ offset_entry + 4, 2].unpack('v')[0]
125
+ metadata_entry = metadata_entries[offset_entry + 8, entry_size - 8]
126
+ if result[metadata_entry_type] == {}
127
+ result[metadata_entry_type] = { metadata_value_type => [
128
+ metadata_entry] }
129
+ else
130
+ if result[metadata_entry_type][metadata_value_type].nil?
131
+ result[metadata_entry_type][metadata_value_type] = [
132
+ metadata_entry]
133
+ else
134
+ result[metadata_entry_type][metadata_value_type] += [
135
+ metadata_entry]
136
+ end
137
+ end
138
+ offset_entry += entry_size
139
+ if metadata_entries[offset_entry, 2] != ''
140
+ entry_size = metadata_entries[offset_entry, 2].unpack('v')[0]
141
+ else
142
+ entry_size = 0
143
+ end
144
+ end
145
+ result
146
+ end
147
+
148
+ # Dummy strcpy to use with metasm and string asignement
149
+ def strcpy(str_src, str_dst)
150
+ (0..(str_src.length - 1)).each do |cpt|
151
+ str_dst[cpt] = str_src[cpt].ord
152
+ end
153
+ end
154
+
155
+ # stretch all the Recovery key and returns it
156
+ def recovery_key_transformation(recoverykey)
157
+ # recovery key stretching phase 1
158
+ recovery_intermediate = recoverykey.split('-').map(&:to_i)
159
+ recovery_intermediate.each do |n|
160
+ n % 11 != 0 && (fail ArgumentError, 'Invalid recovery key')
161
+ end
162
+ recovery_intermediate =
163
+ recovery_intermediate.map { |a| (a / 11) }.pack('v*')
164
+
165
+ # recovery key stretching phase 2
166
+ recovery_keys = []
167
+ cpu = Metasm.const_get('Ia32').new
168
+ exe = Metasm.const_get('Shellcode').new(cpu)
169
+ cp = Metasm::C::Parser.new(exe)
170
+ bitlocker_struct_src = <<-EOS
171
+ typedef struct {
172
+ unsigned char updated_hash[32];
173
+ unsigned char password_hash[32];
174
+ unsigned char salt[16];
175
+ unsigned long long int hash_count;
176
+ } bitlocker_chain_hash_t;
177
+ EOS
178
+ cp.parse bitlocker_struct_src
179
+ btl_struct = Metasm::C::AllocCStruct.new(cp, cp.find_c_struct(
180
+ 'bitlocker_chain_hash_t'))
181
+ vmk_protected_by_recovery_key = @vmk_entries_hash[
182
+ PROTECTION_RECOVERY_PASSWORD]
183
+ if vmk_protected_by_recovery_key.nil?
184
+ fail ArgumentError, 'No recovery key on disk'
185
+ end
186
+ vmk_protected_by_recovery_key.each do |vmk_encrypted|
187
+ vmk_encrypted_raw = vmk_encrypted[ENTRY_TYPE_NONE][
188
+ VALUE_TYPE_STRETCH_KEY][0]
189
+ stretch_key_salt = vmk_encrypted_raw[4, 16]
190
+ strcpy(Digest::SHA256.digest(recovery_intermediate),
191
+ btl_struct.password_hash)
192
+ strcpy(stretch_key_salt, btl_struct.salt)
193
+ btl_struct.hash_count = 0
194
+ sha256 = Digest::SHA256.new
195
+ btl_struct_raw = btl_struct.str
196
+ btl_struct_hash_count_offset = btl_struct.struct.fldoffset[
197
+ 'hash_count']
198
+ (1..0x100000).each do |c|
199
+ updated_hash = sha256.digest(btl_struct_raw)
200
+ btl_struct_raw = updated_hash + btl_struct_raw \
201
+ [btl_struct.updated_hash.sizeof..(
202
+ btl_struct_hash_count_offset - 1)] + [c].pack('Q')
203
+ sha256.reset
204
+ end
205
+ recovery_keys += [btl_struct_raw[btl_struct.updated_hash.stroff,
206
+ btl_struct.updated_hash.sizeof]]
207
+ end
208
+ recovery_keys
209
+ end
210
+
211
+ # Return FVEK entry, encrypted with the VMK
212
+ def fvek_entries
213
+ @fve_metadata_entries[ENTRY_TYPE_FVEK][
214
+ VALUE_TYPE_ENCRYPTED_KEY][ENTRY_TYPE_NONE]
215
+ end
216
+
217
+ # Produce a hash map using the following format:
218
+ # {PROTECTION_TYPE => [fve_entry, fve_entry...]}
219
+ def vmk_entries
220
+ res = {}
221
+ (@fve_metadata_entries[ENTRY_TYPE_VMK][VALUE_TYPE_VMK]).each do |vmk|
222
+ protection_type = vmk[26, 2].unpack('v')[0]
223
+ if res[protection_type].nil?
224
+ res[protection_type] = [fve_entries(vmk[28..-1])]
225
+ else
226
+ res[protection_type] += [fve_entries(vmk[28..-1])]
227
+ end
228
+ end
229
+ res
230
+ end
231
+ end
232
+ end
233
+ end