librex 0.0.70 → 0.0.71

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +5 -13
  2. data/README.markdown +5 -10
  3. data/Rakefile +1 -1
  4. data/lib/rex/arch.rb +1 -1
  5. data/lib/rex/encoder/bloxor/bloxor.rb +1 -0
  6. data/lib/rex/encoder/ndr.rb +1 -1
  7. data/lib/rex/exploitation/heaplib.rb +4 -2
  8. data/lib/rex/exploitation/powershell.rb +62 -0
  9. data/lib/rex/exploitation/powershell/function.rb +63 -0
  10. data/lib/rex/exploitation/powershell/obfu.rb +98 -0
  11. data/lib/rex/exploitation/powershell/output.rb +151 -0
  12. data/lib/rex/exploitation/powershell/param.rb +23 -0
  13. data/lib/rex/exploitation/powershell/parser.rb +183 -0
  14. data/lib/rex/exploitation/powershell/psh_methods.rb +70 -0
  15. data/lib/rex/exploitation/powershell/script.rb +99 -0
  16. data/lib/rex/exploitation/ropdb.rb +1 -0
  17. data/lib/rex/mac_oui.rb +1 -0
  18. data/lib/rex/ole/util.rb +2 -2
  19. data/lib/rex/parser/group_policy_preferences.rb +185 -0
  20. data/lib/rex/parser/outpost24_nokogiri.rb +1 -0
  21. data/lib/rex/poly/machine.rb +1 -0
  22. data/lib/rex/poly/machine/machine.rb +1 -0
  23. data/lib/rex/poly/machine/x86.rb +1 -0
  24. data/lib/rex/post/meterpreter/extensions/android/android.rb +128 -0
  25. data/lib/rex/post/meterpreter/extensions/android/tlv.rb +40 -0
  26. data/lib/rex/post/meterpreter/extensions/stdapi/railgun/def/def_psapi.rb +32 -0
  27. data/lib/rex/post/meterpreter/extensions/stdapi/railgun/dll.rb +6 -6
  28. data/lib/rex/post/meterpreter/extensions/stdapi/railgun/multicall.rb +4 -4
  29. data/lib/rex/post/meterpreter/extensions/stdapi/railgun/railgun.rb +2 -1
  30. data/lib/rex/post/meterpreter/extensions/stdapi/railgun/type/pointer_util.rb +4 -4
  31. data/lib/rex/post/meterpreter/extensions/stdapi/railgun/util.rb +4 -4
  32. data/lib/rex/post/meterpreter/packet.rb +3 -3
  33. data/lib/rex/post/meterpreter/ui/console.rb +2 -0
  34. data/lib/rex/post/meterpreter/ui/console/command_dispatcher/android.rb +383 -0
  35. data/lib/rex/proto/dcerpc/ndr.rb +1 -1
  36. data/lib/rex/proto/ipmi/channel_auth_reply.rb +1 -0
  37. data/lib/rex/proto/ipmi/open_session_reply.rb +1 -0
  38. data/lib/rex/proto/ipmi/rakp2.rb +1 -0
  39. data/lib/rex/proto/natpmp/packet.rb +8 -8
  40. data/lib/rex/proto/ntp.rb +3 -0
  41. data/lib/rex/proto/ntp/constants.rb +12 -0
  42. data/lib/rex/proto/ntp/modes.rb +130 -0
  43. data/lib/rex/proto/pjl.rb +1 -0
  44. data/lib/rex/proto/pjl/client.rb +1 -0
  45. data/lib/rex/proto/sip.rb +4 -0
  46. data/lib/rex/proto/sip/response.rb +61 -0
  47. data/lib/rex/proto/smb/exceptions.rb +11 -3
  48. data/lib/rex/random_identifier_generator.rb +1 -0
  49. data/lib/rex/registry/lfkey.rb +1 -1
  50. data/lib/rex/registry/nodekey.rb +10 -10
  51. data/lib/rex/registry/valuekey.rb +5 -5
  52. data/lib/rex/registry/valuelist.rb +1 -1
  53. data/lib/rex/socket/ip.rb +1 -0
  54. data/lib/rex/sslscan/result.rb +1 -0
  55. data/lib/rex/sslscan/scanner.rb +1 -0
  56. data/lib/rex/text.rb +2 -13
  57. data/lib/rex/ui/text/output/buffer/stdout.rb +1 -0
  58. data/lib/rex/ui/text/table.rb +4 -4
  59. metadata +23 -4
@@ -0,0 +1,23 @@
1
+ # -*- coding: binary -*-
2
+
3
+ module Rex
4
+ module Exploitation
5
+ module Powershell
6
+ class Param
7
+ attr_accessor :klass, :name
8
+ def initialize(klass, name)
9
+ @klass = klass.strip
10
+ @name = name.strip.gsub(/\s|,/, '')
11
+ end
12
+
13
+ #
14
+ # To String
15
+ #
16
+ # @return [String] Powershell param
17
+ def to_s
18
+ "[#{klass}]$#{name}"
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,183 @@
1
+ # -*- coding: binary -*-
2
+
3
+ module Rex
4
+ module Exploitation
5
+ module Powershell
6
+ module Parser
7
+ # Reserved special variables
8
+ # Acquired with: Get-Variable | Format-Table name, value -auto
9
+ RESERVED_VARIABLE_NAMES = [
10
+ '$$',
11
+ '$?',
12
+ '$^',
13
+ '$_',
14
+ '$args',
15
+ '$ConfirmPreference',
16
+ '$ConsoleFileName',
17
+ '$DebugPreference',
18
+ '$Env',
19
+ '$Error',
20
+ '$ErrorActionPreference',
21
+ '$ErrorView',
22
+ '$ExecutionContext',
23
+ '$false',
24
+ '$FormatEnumerationLimit',
25
+ '$HOME',
26
+ '$Host',
27
+ '$input',
28
+ '$LASTEXITCODE',
29
+ '$MaximumAliasCount',
30
+ '$MaximumDriveCount',
31
+ '$MaximumErrorCount',
32
+ '$MaximumFunctionCount',
33
+ '$MaximumHistoryCount',
34
+ '$MaximumVariableCount',
35
+ '$MyInvocation',
36
+ '$NestedPromptLevel',
37
+ '$null',
38
+ '$OutputEncoding',
39
+ '$PID',
40
+ '$PROFILE',
41
+ '$ProgressPreference',
42
+ '$PSBoundParameters',
43
+ '$PSCulture',
44
+ '$PSEmailServer',
45
+ '$PSHOME',
46
+ '$PSSessionApplicationName',
47
+ '$PSSessionConfigurationName',
48
+ '$PSSessionOption',
49
+ '$PSUICulture',
50
+ '$PSVersionTable',
51
+ '$PWD',
52
+ '$ReportErrorShowExceptionClass',
53
+ '$ReportErrorShowInnerException',
54
+ '$ReportErrorShowSource',
55
+ '$ReportErrorShowStackTrace',
56
+ '$ShellId',
57
+ '$StackTrace',
58
+ '$true',
59
+ '$VerbosePreference',
60
+ '$WarningPreference',
61
+ '$WhatIfPreference'
62
+ ].map(&:downcase).freeze
63
+
64
+ #
65
+ # Get variable names from code, removes reserved names from return
66
+ #
67
+ # @return [Array] variable names
68
+ def get_var_names
69
+ our_vars = code.scan(/\$[a-zA-Z\-\_0-9]+/).uniq.flatten.map(&:strip)
70
+ our_vars.select { |v| !RESERVED_VARIABLE_NAMES.include?(v.downcase) }
71
+ end
72
+
73
+ #
74
+ # Get function names from code
75
+ #
76
+ # @return [Array] function names
77
+ def get_func_names
78
+ code.scan(/function\s([a-zA-Z\-\_0-9]+)/).uniq.flatten
79
+ end
80
+
81
+ #
82
+ # Attempt to find string literals in PSH expression
83
+ #
84
+ # @return [Array] string literals
85
+ def get_string_literals
86
+ code.scan(/@"(.+?)"@|@'(.+?)'@/m)
87
+ end
88
+
89
+ #
90
+ # Scan code and return matches with index
91
+ #
92
+ # @param str [String] string to match in code
93
+ # @param source [String] source code to match, defaults to @code
94
+ #
95
+ # @return [Array[String,Integer]] matched items with index
96
+ def scan_with_index(str, source = code)
97
+ ::Enumerator.new do |y|
98
+ source.scan(str) do
99
+ y << ::Regexp.last_match
100
+ end
101
+ end.map { |m| [m.to_s, m.offset(0)[0]] }
102
+ end
103
+
104
+ #
105
+ # Return matching bracket type
106
+ #
107
+ # @param char [String] opening bracket character
108
+ #
109
+ # @return [String] matching closing bracket
110
+ def match_start(char)
111
+ case char
112
+ when '{'
113
+ '}'
114
+ when '('
115
+ ')'
116
+ when '['
117
+ ']'
118
+ when '<'
119
+ '>'
120
+ else
121
+ fail ArgumentError, 'Unknown starting bracket'
122
+ end
123
+ end
124
+
125
+ #
126
+ # Extract block of code inside brackets/parenthesis
127
+ #
128
+ # Attempts to match the bracket at idx, handling nesting manually
129
+ # Once the balanced matching bracket is found, all script content
130
+ # between idx and the index of the matching bracket is returned
131
+ #
132
+ # @param idx [Integer] index of opening bracket
133
+ #
134
+ # @return [String] content between matching brackets
135
+ def block_extract(idx)
136
+ fail ArgumentError unless idx
137
+
138
+ if idx < 0 || idx >= code.length
139
+ fail ArgumentError, 'Invalid index'
140
+ end
141
+
142
+ start = code[idx]
143
+ stop = match_start(start)
144
+ delims = scan_with_index(/#{Regexp.escape(start)}|#{Regexp.escape(stop)}/, code[idx + 1..-1])
145
+ delims.map { |x| x[1] = x[1] + idx + 1 }
146
+ c = 1
147
+ sidx = nil
148
+ # Go through delims till we balance, get idx
149
+ while (c != 0) && (x = delims.shift)
150
+ sidx = x[1]
151
+ x[0] == stop ? c -= 1 : c += 1
152
+ end
153
+
154
+ code[idx..sidx]
155
+ end
156
+
157
+ #
158
+ # Extract a block of function code
159
+ #
160
+ # @param func_name [String] function name
161
+ # @param delete [Boolean] delete the function from the code
162
+ #
163
+ # @return [String] function block
164
+ def get_func(func_name, delete = false)
165
+ start = code.index(func_name)
166
+
167
+ return nil unless start
168
+
169
+ idx = code[start..-1].index('{') + start
170
+ func_txt = block_extract(idx)
171
+
172
+ if delete
173
+ delete_code = code[0..idx]
174
+ delete_code << code[(idx + func_txt.length)..-1]
175
+ @code = delete_code
176
+ end
177
+
178
+ Function.new(func_name, func_txt)
179
+ end
180
+ end # Parser
181
+ end
182
+ end
183
+ end
@@ -0,0 +1,70 @@
1
+ # -*- coding: binary -*-
2
+
3
+ module Rex
4
+ module Exploitation
5
+ module Powershell
6
+ ##
7
+ # Convenience methods for generating powershell code in Ruby
8
+ ##
9
+
10
+ module PshMethods
11
+ #
12
+ # Download file via .NET WebClient
13
+ #
14
+ # @param src [String] URL to the file
15
+ # @param target [String] Location to save the file
16
+ #
17
+ # @return [String] Powershell code to download a file
18
+ def self.download(src, target)
19
+ target ||= '$pwd\\' << src.split('/').last
20
+ %Q^(new-object System.Net.WebClient).DownloadFile("#{src}", "#{target}")^
21
+ end
22
+
23
+ #
24
+ # Uninstall app, or anything named like app
25
+ #
26
+ # @param app [String] Name of application
27
+ # @param fuzzy [Boolean] Whether to apply a fuzzy match (-like) to
28
+ # the application name
29
+ #
30
+ # @return [String] Powershell code to uninstall an application
31
+ def self.uninstall(app, fuzzy = true)
32
+ match = fuzzy ? '-like' : '-eq'
33
+ %Q^$app = Get-WmiObject -Class Win32_Product | Where-Object { $_.Name #{match} "#{app}" }; $app.Uninstall()^
34
+ end
35
+
36
+ #
37
+ # Create secure string from plaintext
38
+ #
39
+ # @param str [String] String to create as a SecureString
40
+ #
41
+ # @return [String] Powershell code to create a SecureString
42
+ def self.secure_string(str)
43
+ %Q(ConvertTo-SecureString -string '#{str}' -AsPlainText -Force$)
44
+ end
45
+
46
+ #
47
+ # Find PID of file lock owner
48
+ #
49
+ # @param filename [String] Filename
50
+ #
51
+ # @return [String] Powershell code to identify the PID of a file
52
+ # lock owner
53
+ def self.who_locked_file(filename)
54
+ %Q^ Get-Process | foreach{$processVar = $_;$_.Modules | foreach{if($_.FileName -eq "#{filename}"){$processVar.Name + " PID:" + $processVar.id}}}^
55
+ end
56
+
57
+ #
58
+ # Return last time of login
59
+ #
60
+ # @param user [String] Username
61
+ #
62
+ # @return [String] Powershell code to return the last time of a user
63
+ # login
64
+ def self.get_last_login(user)
65
+ %Q^ Get-QADComputer -ComputerRole DomainController | foreach { (Get-QADUser -Service $_.Name -SamAccountName "#{user}").LastLogon} | Measure-Latest^
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,99 @@
1
+ # -*- coding: binary -*-
2
+
3
+ require 'rex'
4
+ require 'forwardable'
5
+
6
+ module Rex
7
+ module Exploitation
8
+ module Powershell
9
+ class Script
10
+ attr_accessor :code
11
+ attr_reader :functions, :rig
12
+
13
+ include Output
14
+ include Parser
15
+ include Obfu
16
+ # Pretend we are actually a string
17
+ extend ::Forwardable
18
+ # In case someone messes with String we delegate based on its instance methods
19
+ # eval %Q|def_delegators :@code, :#{::String.instance_methods[0..(String.instance_methods.index(:class)-1)].join(', :')}|
20
+ def_delegators :@code, :each_line, :strip, :chars, :intern, :chr, :casecmp, :ascii_only?, :<, :tr_s,
21
+ :!=, :capitalize!, :ljust, :to_r, :sum, :private_methods, :gsub, :dump, :match, :to_sym,
22
+ :enum_for, :display, :tr_s!, :freeze, :gsub, :split, :rindex, :<<, :<=>, :+, :lstrip!,
23
+ :encoding, :start_with?, :swapcase, :lstrip!, :encoding, :start_with?, :swapcase,
24
+ :each_byte, :lstrip, :codepoints, :insert, :getbyte, :swapcase!, :delete, :rjust, :>=,
25
+ :!, :count, :slice, :clone, :chop!, :prepend, :succ!, :upcase, :include?, :frozen?,
26
+ :delete!, :chop, :lines, :replace, :next, :=~, :==, :rstrip!, :%, :upcase!, :each_char,
27
+ :hash, :rstrip, :length, :reverse, :setbyte, :bytesize, :squeeze, :>, :center, :[],
28
+ :<=, :to_c, :slice!, :chomp!, :next!, :downcase, :unpack, :crypt, :partition,
29
+ :between?, :squeeze!, :to_s, :chomp, :bytes, :clear, :!~, :to_i, :valid_encoding?, :===,
30
+ :tr, :downcase!, :scan, :sub!, :each_codepoint, :reverse!, :class, :size, :empty?, :byteslice,
31
+ :initialize_clone, :to_str, :to_enum, :tap, :tr!, :trust, :encode!, :sub, :oct, :succ, :index,
32
+ :[]=, :encode, :*, :hex, :to_f, :strip!, :rpartition, :ord, :capitalize, :upto, :force_encoding,
33
+ :end_with?
34
+
35
+ def initialize(code)
36
+ @code = ''
37
+ @rig = Rex::RandomIdentifierGenerator.new
38
+
39
+ begin
40
+ # Open code file for reading
41
+ fd = ::File.new(code, 'rb')
42
+ while (line = fd.gets)
43
+ @code << line
44
+ end
45
+
46
+ # Close open file
47
+ fd.close
48
+ rescue Errno::ENAMETOOLONG, Errno::ENOENT
49
+ # Treat code as a... code
50
+ @code = code.to_s.dup # in case we're eating another script
51
+ end
52
+ @functions = get_func_names.map { |f| get_func(f) }
53
+ end
54
+
55
+ ##
56
+ # Class methods
57
+ ##
58
+
59
+ #
60
+ # Convert binary to byte array, read from file if able
61
+ #
62
+ # @param input_data [String] Path to powershell file or powershell
63
+ # code string
64
+ # @param var_name [String] Byte array variable name
65
+ #
66
+ # @return [String] input_data as a powershell byte array
67
+ def self.to_byte_array(input_data, var_name = Rex::Text.rand_text_alpha(rand(3) + 3))
68
+ # File will raise an exception if the path contains null byte
69
+ if input_data.include? "\x00"
70
+ code = input_data
71
+ else
72
+ code = ::File.file?(input_data) ? ::File.read(input_data) : input_data
73
+ end
74
+
75
+ code = code.unpack('C*')
76
+ psh = "[Byte[]] $#{var_name} = 0x#{code[0].to_s(16)}"
77
+ lines = []
78
+ 1.upto(code.length - 1) do |byte|
79
+ if (byte % 10 == 0)
80
+ lines.push "\r\n$#{var_name} += 0x#{code[byte].to_s(16)}"
81
+ else
82
+ lines.push ",0x#{code[byte].to_s(16)}"
83
+ end
84
+ end
85
+
86
+ psh << lines.join('') + "\r\n"
87
+ end
88
+
89
+ #
90
+ # Return list of code modifier methods
91
+ #
92
+ # @return [Array] Code modifiers
93
+ def self.code_modifiers
94
+ instance_methods.select { |m| m =~ /^(strip|sub)/ }
95
+ end
96
+ end # class Script
97
+ end
98
+ end
99
+ end
@@ -1,3 +1,4 @@
1
+ # -*- coding: binary -*-
1
2
  require 'rex/text'
2
3
  require 'rexml/document'
3
4
 
@@ -1,3 +1,4 @@
1
+ # -*- coding: binary -*-
1
2
 
2
3
  module Rex
3
4
  module Oui
@@ -124,7 +124,7 @@ class Util
124
124
 
125
125
 
126
126
  def self.getUnicodeString(buf)
127
- buf = buf.unpack('S*').pack('C*')
127
+ buf = buf.unpack('v*').pack('C*')
128
128
  if (idx = buf.index(0x00.chr))
129
129
  buf.slice!(idx, buf.length)
130
130
  end
@@ -132,7 +132,7 @@ class Util
132
132
  end
133
133
 
134
134
  def self.putUnicodeString(buf)
135
- buf = buf.unpack('C*').pack('S*')
135
+ buf = buf.unpack('C*').pack('v*')
136
136
  if (buf.length < 0x40)
137
137
  buf << "\x00" * (0x40 - buf.length)
138
138
  end
@@ -0,0 +1,185 @@
1
+ # -*- coding: binary -*-
2
+ #
3
+
4
+ module Rex
5
+ module Parser
6
+
7
+ # This is a parser for the Windows Group Policy Preferences file
8
+ # format. It's used by modules/post/windows/gather/credentials/gpp.rb
9
+ # and uses REXML (as opposed to Nokogiri) for its XML parsing.
10
+ # See: http://msdn.microsoft.com/en-gb/library/cc232587.aspx
11
+ class GPP
12
+ require 'rex'
13
+ require 'rexml/document'
14
+
15
+ def self.parse(data)
16
+ if data.nil?
17
+ return []
18
+ end
19
+
20
+ xml = REXML::Document.new(data).root
21
+ results = []
22
+
23
+ unless xml and xml.elements and xml.elements.to_a("//Properties")
24
+ return []
25
+ end
26
+
27
+ xml.elements.to_a("//Properties").each do |node|
28
+ epassword = node.attributes['cpassword']
29
+ next if epassword.to_s.empty?
30
+ password = self.decrypt(epassword)
31
+
32
+ user = node.attributes['runAs'] if node.attributes['runAs']
33
+ user = node.attributes['accountName'] if node.attributes['accountName']
34
+ user = node.attributes['username'] if node.attributes['username']
35
+ user = node.attributes['userName'] if node.attributes['userName']
36
+ user = node.attributes['newName'] unless node.attributes['newName'].nil? || node.attributes['newName'].empty?
37
+ changed = node.parent.attributes['changed']
38
+
39
+ # Printers and Shares
40
+ path = node.attributes['path']
41
+
42
+ # Datasources
43
+ dsn = node.attributes['dsn']
44
+ driver = node.attributes['driver']
45
+
46
+ # Tasks
47
+ app_name = node.attributes['appName']
48
+
49
+ # Services
50
+ service = node.attributes['serviceName']
51
+
52
+ # Groups
53
+ expires = node.attributes['expires']
54
+ never_expires = node.attributes['neverExpires']
55
+ disabled = node.attributes['acctDisabled']
56
+
57
+ result = {
58
+ :USER => user,
59
+ :PASS => password,
60
+ :CHANGED => changed
61
+ }
62
+
63
+ result.merge!({ :EXPIRES => expires }) unless expires.nil? || expires.empty?
64
+ result.merge!({ :NEVER_EXPIRES => never_expires.to_i }) unless never_expires.nil? || never_expires.empty?
65
+ result.merge!({ :DISABLED => disabled.to_i }) unless disabled.nil? || disabled.empty?
66
+ result.merge!({ :PATH => path }) unless path.nil? || path.empty?
67
+ result.merge!({ :DATASOURCE => dsn }) unless dsn.nil? || dsn.empty?
68
+ result.merge!({ :DRIVER => driver }) unless driver.nil? || driver.empty?
69
+ result.merge!({ :TASK => app_name }) unless app_name.nil? || app_name.empty?
70
+ result.merge!({ :SERVICE => service }) unless service.nil? || service.empty?
71
+
72
+ attributes = []
73
+ node.elements.each('//Attributes//Attribute') do |dsn_attribute|
74
+ attributes << {
75
+ :A_NAME => dsn_attribute.attributes['name'],
76
+ :A_VALUE => dsn_attribute.attributes['value']
77
+ }
78
+ end
79
+
80
+ result.merge!({ :ATTRIBUTES => attributes }) unless attributes.empty?
81
+
82
+ results << result
83
+ end
84
+
85
+ results
86
+ end
87
+
88
+ def self.create_tables(results, filetype, domain=nil, dc=nil)
89
+ tables = []
90
+ results.each do |result|
91
+ table = Rex::Ui::Text::Table.new(
92
+ 'Header' => 'Group Policy Credential Info',
93
+ 'Indent' => 1,
94
+ 'SortIndex' => -1,
95
+ 'Columns' =>
96
+ [
97
+ 'Name',
98
+ 'Value',
99
+ ]
100
+ )
101
+
102
+ table << ["TYPE", filetype]
103
+ table << ["USERNAME", result[:USER]]
104
+ table << ["PASSWORD", result[:PASS]]
105
+ table << ["DOMAIN CONTROLLER", dc] unless dc.nil? || dc.empty?
106
+ table << ["DOMAIN", domain] unless domain.nil? || domain.empty?
107
+ table << ["CHANGED", result[:CHANGED]]
108
+ table << ["EXPIRES", result[:EXPIRES]] unless result[:EXPIRES].nil? || result[:EXPIRES].empty?
109
+ table << ["NEVER_EXPIRES?", result[:NEVER_EXPIRES]] unless result[:NEVER_EXPIRES].nil?
110
+ table << ["DISABLED", result[:DISABLED]] unless result[:DISABLED].nil?
111
+ table << ["PATH", result[:PATH]] unless result[:PATH].nil? || result[:PATH].empty?
112
+ table << ["DATASOURCE", result[:DSN]] unless result[:DSN].nil? || result[:DSN].empty?
113
+ table << ["DRIVER", result[:DRIVER]] unless result[:DRIVER].nil? || result[:DRIVER].empty?
114
+ table << ["TASK", result[:TASK]] unless result[:TASK].nil? || result[:TASK].empty?
115
+ table << ["SERVICE", result[:SERVICE]] unless result[:SERVICE].nil? || result[:SERVICE].empty?
116
+
117
+ unless result[:ATTRIBUTES].nil? || result[:ATTRIBUTES].empty?
118
+ result[:ATTRIBUTES].each do |dsn_attribute|
119
+ table << ["ATTRIBUTE", "#{dsn_attribute[:A_NAME]} - #{dsn_attribute[:A_VALUE]}"]
120
+ end
121
+ end
122
+
123
+ tables << table
124
+ end
125
+
126
+ tables
127
+ end
128
+
129
+ # Decrypts passwords using Microsoft's published key:
130
+ # http://msdn.microsoft.com/en-us/library/cc422924.aspx
131
+ def self.decrypt(encrypted_data)
132
+ password = ""
133
+ return password unless encrypted_data
134
+
135
+ password = ""
136
+ retries = 0
137
+ original_data = encrypted_data.dup
138
+
139
+ begin
140
+ mod = encrypted_data.length % 4
141
+
142
+ # PowerSploit code strips the last character, unsure why...
143
+ case mod
144
+ when 1
145
+ encrypted_data = encrypted_data[0..-2]
146
+ when 2, 3
147
+ padding = '=' * (4 - mod)
148
+ encrypted_data = "#{encrypted_data}#{padding}"
149
+ end
150
+
151
+ # Strict base64 decoding used here
152
+ decoded = encrypted_data.unpack('m0').first
153
+ rescue ::ArgumentError => e
154
+ # Appears to be some junk UTF-8 Padding appended at times in
155
+ # Win2k8 (not in Win2k8R2)
156
+ # Lets try stripping junk and see if we can decrypt
157
+ if retries < 8
158
+ retries += 1
159
+ original_data = original_data[0..-2]
160
+ encrypted_data = original_data
161
+ retry
162
+ else
163
+ return password
164
+ end
165
+ end
166
+
167
+ key = "\x4e\x99\x06\xe8\xfc\xb6\x6c\xc9\xfa\xf4\x93\x10\x62\x0f\xfe\xe8\xf4\x96\xe8\x06\xcc\x05\x79\x90\x20\x9b\x09\xa4\x33\xb6\x6c\x1b"
168
+ aes = OpenSSL::Cipher::Cipher.new("AES-256-CBC")
169
+ begin
170
+ aes.decrypt
171
+ aes.key = key
172
+ plaintext = aes.update(decoded)
173
+ plaintext << aes.final
174
+ password = plaintext.unpack('v*').pack('C*') # UNICODE conversion
175
+ rescue OpenSSL::Cipher::CipherError => e
176
+ puts "Unable to decode: \"#{encrypted_data}\" Exception: #{e}"
177
+ end
178
+
179
+ password
180
+ end
181
+
182
+ end
183
+ end
184
+ end
185
+